Compare commits

..

1 Commits

Author SHA1 Message Date
bill-rich 1bbc35e5be Testing 2023-03-07 08:39:18 -08:00
1263 changed files with 9127 additions and 50964 deletions
+5 -1
View File
@@ -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. --->
+14 -8
View File
@@ -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 the problem is. Ex. I'm always frustrated when [...] --->
## Description of the Preferred Solution
<!--- A clear and concise description of what you want to happen. What <!--- 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? 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
-8
View File
@@ -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))?
-7
View File
@@ -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.
+16
View File
@@ -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"
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"prConcurrentLimit": 3,
"prHourlyLimit": 2
}
+1 -1
View File
@@ -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
-46
View File
@@ -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
+9 -4
View File
@@ -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,7 +30,10 @@ 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
-92
View File
@@ -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
+7 -10
View File
@@ -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
+4 -5
View File
@@ -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 }}
@@ -29,4 +29,3 @@ jobs:
path: ./ path: ./
base: ${{ github.event.repository.default_branch }} base: ${{ github.event.repository.default_branch }}
head: HEAD head: HEAD
extra_args: --only-verified
+6 -3
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
-12
View File
@@ -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
-10
View File
@@ -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
View File
@@ -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
+4 -3
View File
@@ -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
+5 -8
View File
@@ -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,14 +49,14 @@ 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
+137 -243
View File
@@ -8,6 +8,7 @@
<div align="center"> <div align="center">
[![CI Status](https://github.com/trufflesecurity/trufflehog/actions/workflows/release.yml/badge.svg)](https://github.com/trufflesecurity/trufflehog/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/trufflesecurity/trufflehog/v3)](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3) [![Go Report Card](https://goreportcard.com/badge/github.com/trufflesecurity/trufflehog/v3)](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)
[![License](https://img.shields.io/badge/license-AGPL--3.0-brightgreen)](/LICENSE) [![License](https://img.shields.io/badge/license-AGPL--3.0-brightgreen)](/LICENSE)
[![Total Detectors](https://img.shields.io/github/directory-file-count/trufflesecurity/truffleHog/pkg/detectors?label=Total%20Detectors&type=dir)](/pkg/detectors) [![Total Detectors](https://img.shields.io/github/directory-file-count/trufflesecurity/truffleHog/pkg/detectors?label=Total%20Detectors&type=dir)](/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
![GitHub scanning demo](https://storage.googleapis.com/truffle-demos/non-interactive.svg) ![GitHub scanning demo](https://storage.googleapis.com/truffle-demos/non-interactive.svg)
@@ -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.
-1
View File
@@ -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

-14
View File
@@ -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}'
```
-15
View File
@@ -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|;]|$)"
+84 -201
View File
@@ -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
) )
+177 -756
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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
-10
View File
@@ -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
-15
View File
@@ -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
-24
View File
@@ -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

-80
View File
@@ -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
+5 -34
View File
@@ -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).
+27 -74
View File
@@ -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
```
+5 -5
View File
@@ -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":
+4 -4
View File
@@ -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")
} }
+148 -320
View File
@@ -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{}
} }
-24
View File
@@ -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
}
-105
View File
@@ -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, ",")
}
-169
View File
@@ -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
}
-115
View File
@@ -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
}
}
}
-30
View File
@@ -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)
}
-17
View File
@@ -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
}
}
}
-120
View File
@@ -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")
}
-138
View File
@@ -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
View File
@@ -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
} }
-8
View File
@@ -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
View File
@@ -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)}
}
-59
View File
@@ -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)
}
+2 -1
View File
@@ -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) {
-61
View File
@@ -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
}
-59
View File
@@ -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
View File
@@ -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
} }
} }
-36
View File
@@ -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")
}
}
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())
} }
+13 -17
View File
@@ -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
View File
@@ -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
}
+8 -78
View File
@@ -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 -11
View File
@@ -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.
-48
View File
@@ -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.
-94
View File
@@ -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
View File
@@ -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
} }
-1
View File
@@ -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"
) )
-1
View File
@@ -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
View File
@@ -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 {
-1
View File
@@ -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 {
-1
View File
@@ -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)
} }
-1
View File
@@ -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 {
-2
View File
@@ -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 {
-1
View File
@@ -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 -27
View File
@@ -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 {
-89
View File
@@ -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
}
-162
View File
@@ -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)
}
}
})
}
}
-1
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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))]
} }
-2
View File
@@ -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 {
-1
View File
@@ -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 {
-1
View File
@@ -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 {
@@ -112,7 +112,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