Compare commits
364 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52bb5d44f8 | |||
| 825c751775 | |||
| 3e4496156c | |||
| d382d5cb1c | |||
| c617bd7a4e | |||
| 6193509098 | |||
| 3c1bb45bfb | |||
| 0fe9bf0868 | |||
| f0b6b5d0d9 | |||
| ef9488c77d | |||
| e105126227 | |||
| a7abd6231d | |||
| 17e8d7030e | |||
| af24d36ce8 | |||
| 597b618fd9 | |||
| 8076067b30 | |||
| 585bd82d47 | |||
| 2b1c42ceb1 | |||
| 2f61e4f5aa | |||
| cbf299aa77 | |||
| 3fdef756f2 | |||
| 638ff804f1 | |||
| d5cbd7b999 | |||
| c8077145d5 | |||
| 5c99a1e754 | |||
| e6846ede54 | |||
| 4500ac3b10 | |||
| 4777b77ec6 | |||
| c72840de67 | |||
| aa47e5e248 | |||
| 0cf9139df6 | |||
| 86370333ec | |||
| 64c163cd90 | |||
| 3870be256c | |||
| 2315192fda | |||
| 6209a80ce1 | |||
| 4efe5313f4 | |||
| 7373954ddf | |||
| d2d03426ed | |||
| dd39848709 | |||
| c5b4d6f28b | |||
| ae2d510ced | |||
| 05f6bd369f | |||
| d151c1363e | |||
| ee5b028c67 | |||
| 38562df0f6 | |||
| 4f0e66f064 | |||
| 0b4e3a66b1 | |||
| a293033386 | |||
| d20f43b5c6 | |||
| 9ef9e9870d | |||
| f1582aafa9 | |||
| 9158dcaa80 | |||
| 012fdfe3a2 | |||
| ea71756e20 | |||
| ea40c0f306 | |||
| bcecbcd3d4 | |||
| 926f490c9f | |||
| bd17aa91e3 | |||
| 161e499142 | |||
| c6826c4574 | |||
| f2139a7615 | |||
| 26c455d302 | |||
| 57983aed4e | |||
| 4f13090c01 | |||
| ae14e4506f | |||
| 0c66d30c1f | |||
| 67784f6928 | |||
| c5c8d10d28 | |||
| d317ddb51a | |||
| 29be679370 | |||
| 0ce72ccda3 | |||
| 58e8c1e4ac | |||
| 114f4b6989 | |||
| e47cc2451f | |||
| 1f0fd91205 | |||
| 0d73dbe638 | |||
| 7dd2b74f1f | |||
| b37080e6a5 | |||
| 3f30216356 | |||
| af6e3f8fdf | |||
| 2d35edfb23 | |||
| 8dedd08d89 | |||
| 8be89a593b | |||
| 80a68b84c2 | |||
| 58b78b6a5a | |||
| a53758c4c4 | |||
| 1d3206ed36 | |||
| 26afa76e7c | |||
| 8df9db6ecc | |||
| e85411b59f | |||
| 6e58eb4bbb | |||
| b29b78c10d | |||
| d4f4b1c73e | |||
| 539be34752 | |||
| 00ebb2ed64 | |||
| 4ef546a06b | |||
| ac1dd23d37 | |||
| 2088f030f9 | |||
| 2060ae1c47 | |||
| 86f80fc5a8 | |||
| c62eb18e6d | |||
| 8acd9fdd3b | |||
| 1621403e11 | |||
| 319ae64a02 | |||
| bc27fef7bc | |||
| 45b02f46d9 | |||
| 0aa8e1cd98 | |||
| 477e2a1332 | |||
| 430d5c764c | |||
| 8b2e1d36cf | |||
| 864cf00337 | |||
| e5ede17c77 | |||
| 74831f63d5 | |||
| 09d4422cdb | |||
| 7de0178842 | |||
| 6d384ce3e8 | |||
| 0e24d406d5 | |||
| d72c31b8b6 | |||
| d720c0c0f3 | |||
| 705c01e5f3 | |||
| fc6fd29f3f | |||
| ee6817ad85 | |||
| 5f6143f09a | |||
| 3fadec950e | |||
| 330a6f7cdc | |||
| 009756dce6 | |||
| 8859771a2a | |||
| 130d5ae3ad | |||
| f5b83ee2a5 | |||
| cc6bd31586 | |||
| e3b6de0fdc | |||
| 936a139596 | |||
| e091fab94f | |||
| 861ad057c7 | |||
| 36ca2601e0 | |||
| 7ac7fdae44 | |||
| 6dd0441f6c | |||
| a0b8edd987 | |||
| 26befdd1ec | |||
| 4020c4002b | |||
| aada296ddc | |||
| f3249009f7 | |||
| 544359eee6 | |||
| 7de9bdd12d | |||
| a72b9feb35 | |||
| f008d4bead | |||
| 6ee3000e53 | |||
| d96b7f8468 | |||
| 335ce85ce4 | |||
| 4e2ce4f6fe | |||
| 33d32d2de4 | |||
| 1a1c2e275e | |||
| 9f99ee470d | |||
| c3e596e853 | |||
| 2a2bcd93ac | |||
| 5a339b0ca1 | |||
| 3b055ce3f9 | |||
| c26142d898 | |||
| f1ec9e74eb | |||
| 45ebafff61 | |||
| 79cae3b82b | |||
| 28dd25beeb | |||
| 4409210b87 | |||
| 054e98d108 | |||
| 6e25664a52 | |||
| b45369cdbb | |||
| 9757c339d9 | |||
| ae4b387448 | |||
| b18edef01a | |||
| 87e7aa3970 | |||
| b3d3f531a4 | |||
| eb4ff435a5 | |||
| b8be0a64a8 | |||
| d3b550a2e3 | |||
| e5ba2c9a3c | |||
| 1228127d12 | |||
| 86f9e1288f | |||
| 696f5c68f4 | |||
| 3d501975e4 | |||
| 64cfe4d85e | |||
| 741130fb8d | |||
| d3b24fa592 | |||
| 42a82fc7e1 | |||
| aead0425d9 | |||
| bb0fa055dc | |||
| ecd25784f5 | |||
| 02ed33d1c1 | |||
| 5f0964add8 | |||
| 28983036a0 | |||
| e8cd2e7fae | |||
| dd141fb55f | |||
| 3a143f095b | |||
| ab54ec4072 | |||
| 85f5f3ea7b | |||
| a7fc12240f | |||
| fe1e475a04 | |||
| 2884864cd3 | |||
| 2ac483da1f | |||
| fe029b1098 | |||
| c91158d8f7 | |||
| 6696c85d32 | |||
| 4a458c9eab | |||
| 965279421c | |||
| ab71b93f7d | |||
| 034ca4fb5b | |||
| 0c81cba918 | |||
| ca8a5ef741 | |||
| 4f83dd816d | |||
| 33c6c193e3 | |||
| 466b9e2d6b | |||
| dac40519e4 | |||
| 60464da3ce | |||
| d7d614cc5f | |||
| d5fef0f3e1 | |||
| 958266ea84 | |||
| 3d5f697f9a | |||
| 2a58268e42 | |||
| 64ace363af | |||
| ce36383513 | |||
| 2e1006e1a7 | |||
| 477b461e18 | |||
| 68740953cd | |||
| 46bc010165 | |||
| fb56b9f713 | |||
| 029519eb01 | |||
| c203eef86f | |||
| 2d6aadcb46 | |||
| d29357c9d4 | |||
| 04c9bb535e | |||
| 4aab7b7276 | |||
| cea2a23c56 | |||
| 785cead43e | |||
| 85467538f6 | |||
| 128002885a | |||
| 3f6e5b44c9 | |||
| 2bc4985061 | |||
| ad4b9406a7 | |||
| 80dcfbe9db | |||
| 02310a64f3 | |||
| 891996f546 | |||
| c88e84d3f0 | |||
| 1c00014051 | |||
| 97a73710de | |||
| 752c848640 | |||
| db42bcf2a2 | |||
| 336fe8d511 | |||
| 3645a6e7b9 | |||
| e3107ad6bb | |||
| f2f5b6dba8 | |||
| 92f40c2031 | |||
| e8f1bb9127 | |||
| 63fcf33ce6 | |||
| 509cf8b6fa | |||
| ddc81bd7c1 | |||
| b02cf7e032 | |||
| 335e676caa | |||
| 593f1e6754 | |||
| fcd580406e | |||
| 4382fd3441 | |||
| 44bc023da6 | |||
| 59d6d29c02 | |||
| 57e46f9b76 | |||
| 3e414a3ba8 | |||
| a30b52f9b0 | |||
| c4492b1fdc | |||
| e9599db240 | |||
| 33ab1cfeb2 | |||
| 945de06858 | |||
| 912d8e461d | |||
| 248cff8201 | |||
| 3fe916fe1e | |||
| ecfdb0105b | |||
| 80b247286b | |||
| 7ba583ca40 | |||
| c12be4d98d | |||
| 5be6b1e340 | |||
| 4695a4e3a9 | |||
| 67e8df96a4 | |||
| 33ff9178e4 | |||
| 41936169c7 | |||
| b9d6f11609 | |||
| d7123c6965 | |||
| 65a7855713 | |||
| aba56523b6 | |||
| 90da460fa1 | |||
| cc2df10e49 | |||
| 5db6237f2a | |||
| 4c3c103b62 | |||
| ad1c96c151 | |||
| a35786dccd | |||
| 18bca4b442 | |||
| 79ebd68068 | |||
| d11ce27f33 | |||
| 37c4eea66a | |||
| fa2d6b90cd | |||
| 8b6eee73bc | |||
| fa9479100e | |||
| f3367d7910 | |||
| 2452e93a80 | |||
| c8ac7c392e | |||
| 20cdcbc970 | |||
| 098d4a9e7d | |||
| 8709b4fdc6 | |||
| 024c847f83 | |||
| 8a9229cf73 | |||
| 77fabe6191 | |||
| 6f07c59354 | |||
| e192aee66a | |||
| 64f15a7bfe | |||
| ebd6b5565b | |||
| 4982755db2 | |||
| 7ccf69d419 | |||
| 45aaa25fe8 | |||
| 0063d50652 | |||
| 956a58fd95 | |||
| 38f6cc07ea | |||
| bfa5e642a3 | |||
| c2d42878c8 | |||
| 3d04abced8 | |||
| b79b8e4ec7 | |||
| be4fedbcb4 | |||
| 608eb45797 | |||
| da1d3b3a01 | |||
| 0ff5cdd623 | |||
| cded7a5489 | |||
| ea3aba852d | |||
| e9f4cf99e5 | |||
| 0d8a154330 | |||
| 3131253b78 | |||
| 0ddd49a1b8 | |||
| 4cc3529bc5 | |||
| df53dd5a5b | |||
| a0d44a39f1 | |||
| 619ab2027a | |||
| 25082e63a6 | |||
| 5ad3bbde37 | |||
| af34a6a108 | |||
| 67004fe68a | |||
| 2cab951ee5 | |||
| 73f9d3f0a0 | |||
| 2bd17052bb | |||
| 4a93e49eea | |||
| a473b9aa99 | |||
| dcc102a81c | |||
| 30ebe84e3e | |||
| 8826e369cf | |||
| dfb7e9a405 | |||
| d8ed02643a | |||
| b081766d9b | |||
| b0a58fcf72 | |||
| 7273dc9058 | |||
| 047e2b4607 | |||
| 29f12d2d35 | |||
| 4a3bba78cc | |||
| dc516933f3 | |||
| ff8cf8fa09 | |||
| 5b7eff5c57 | |||
| 21e1ff4a8a | |||
| 0752503872 | |||
| 4b0315c90a | |||
| 176552b07a | |||
| 2515fbeebe | |||
| 3b655870c4 |
@@ -0,0 +1,19 @@
|
||||
# Testing
|
||||
|
||||
Most testing is handled automatically by our GitHub Actions workflows.
|
||||
|
||||
## Local GitHub Action Testing
|
||||
|
||||
In some cases you may wish to submit changes to the Trufflehog GitHub Action. Unfortunately GitHub does not provide a 1st-party testing environment for testing actions outside of GitHub Actions.
|
||||
|
||||
Fortunately [nektos/act](https://github.com/nektos/act) enables local testing of GitHub Actions.
|
||||
|
||||
### Instructions
|
||||
|
||||
1. Please follow [the installation instructions](http://https://github.com/nektos/act#installation) for your OS.
|
||||
2. The first run of `act` will ask you to specify an image. `Medium` should suffice.
|
||||
3. You'll need to configure a personal-access-token(PAT) with: `repo:status`, `repo_deployment`, and `public_repo` permissions.
|
||||
4. Set an environment variable named `GITHUB_TOKEN` with the PAT from the previous step as the value: `$ export GITHUB_TOKEN=<your_PAT>`
|
||||
5. Run the following command from the repository root: `act pull_request -j test -W .github/workflows/secrets.yml -s GITHUB_TOKEN --defaultbranch main`
|
||||
6. If the job was successful, you should expect to see output from the scanner showing several detected secrets.
|
||||
7. If you want to omit the context of a pull request event and just test that the action starts successfully, run: `act -j test -W .github/workflows/secrets.yml -s GITHUB_TOKEN --defaultbranch main`
|
||||
@@ -1,16 +1,9 @@
|
||||
name: Release
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# tags:
|
||||
# - 'v*'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -44,7 +37,7 @@ jobs:
|
||||
with:
|
||||
go-version: '1.18'
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
name: Smoke
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Smoke
|
||||
run: |
|
||||
set -e
|
||||
go run . git https://github.com/dustin-decker/secretsandstuff.git
|
||||
go run . github --repo https://github.com/dustin-decker/secretsandstuff.git
|
||||
@@ -23,12 +23,12 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- id: 'auth'
|
||||
uses: 'google-github-actions/auth@v0'
|
||||
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
|
||||
run: make test-integration
|
||||
test-detectors:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- id: 'auth'
|
||||
uses: 'google-github-actions/auth@v0'
|
||||
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'
|
||||
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
.idea
|
||||
dist
|
||||
.env
|
||||
*.test
|
||||
*.test
|
||||
|
||||
# binary
|
||||
trufflehog
|
||||
|
||||
@@ -13,6 +13,8 @@ builds:
|
||||
dockers:
|
||||
- image_templates: ["trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64"]
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
extra_files:
|
||||
- entrypoint.sh
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- --platform=linux/amd64
|
||||
@@ -26,6 +28,8 @@ dockers:
|
||||
- image_templates: ["trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8"]
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
extra_files:
|
||||
- entrypoint.sh
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- --platform=linux/arm64/v8
|
||||
@@ -38,6 +42,8 @@ dockers:
|
||||
- --label=org.opencontainers.image.licenses=AGPL-3.0
|
||||
- image_templates: ["ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64"]
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
extra_files:
|
||||
- entrypoint.sh
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- --platform=linux/amd64
|
||||
@@ -51,6 +57,8 @@ dockers:
|
||||
- image_templates: ["ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8"]
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
extra_files:
|
||||
- entrypoint.sh
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- --platform=linux/arm64/v8
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
- id: trufflehog
|
||||
name: TruffleHog
|
||||
description: Detect secrets in your data with TruffleHog.
|
||||
entry: trufflehog git file://. --since-commit HEAD --only-verified --fail
|
||||
language: golang
|
||||
pass_filenames: false
|
||||
+12
-7
@@ -1,13 +1,18 @@
|
||||
FROM golang:bullseye as builder
|
||||
FROM --platform=${BUILDPLATFORM} golang:bullseye as builder
|
||||
|
||||
WORKDIR /build
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -a -o trufflehog main.go
|
||||
ENV CGO_ENABLED=0
|
||||
ARG TARGETOS TARGETARCH
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
|
||||
|
||||
FROM alpine:3.15
|
||||
RUN apk add --no-cache git
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
RUN apk add --no-cache bash git openssh-client ca-certificates \
|
||||
&& rm -rf /var/cache/apk/* && \
|
||||
update-ca-certificates
|
||||
COPY --from=builder /build/trufflehog /usr/bin/trufflehog
|
||||
ENTRYPOINT ["/usr/bin/trufflehog"]
|
||||
COPY entrypoint.sh /etc/entrypoint.sh
|
||||
RUN chmod +x /etc/entrypoint.sh
|
||||
ENTRYPOINT ["/etc/entrypoint.sh"]
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
FROM alpine:3.15
|
||||
|
||||
RUN apk add --no-cache git
|
||||
RUN apk add --no-cache bash git openssh-client ca-certificates \
|
||||
&& rm -rf /var/cache/apk/* && \
|
||||
update-ca-certificates
|
||||
WORKDIR /usr/bin/
|
||||
COPY trufflehog .
|
||||
|
||||
ENTRYPOINT ["/usr/bin/trufflehog"]
|
||||
COPY entrypoint.sh /etc/entrypoint.sh
|
||||
RUN chmod +x /etc/entrypoint.sh
|
||||
ENTRYPOINT ["/etc/entrypoint.sh"]
|
||||
|
||||
@@ -22,19 +22,22 @@ check:
|
||||
go vet $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
lint:
|
||||
golangci-lint run --out-format=colored-line-number --timeout 10m
|
||||
golangci-lint run --enable bodyclose --out-format=colored-line-number --timeout 10m
|
||||
|
||||
test-failing:
|
||||
CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/) | grep FAIL
|
||||
|
||||
test:
|
||||
CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/)
|
||||
CGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/ | grep -v pkg/detectors)
|
||||
|
||||
test-integration:
|
||||
CGO_ENABLED=0 go test -timeout=5m -tags=integration $(shell go list ./... | grep -v /vendor/ | grep -v pkg/detectors)
|
||||
|
||||
test-race:
|
||||
CGO_ENABLED=1 go test -timeout=5m -race $(shell go list ./... | grep -v /vendor/)
|
||||
CGO_ENABLED=1 go test -timeout=5m -race $(shell go list ./... | grep -v /vendor/ | grep -v pkg/detectors)
|
||||
|
||||
test-detectors:
|
||||
CGO_ENABLED=0 go test -tags=detectors -timeout=5m $(shell go list ./...)
|
||||
CGO_ENABLED=0 go test -tags=detectors -timeout=5m $(shell go list ./... | grep pkg/detectors)
|
||||
|
||||
bench:
|
||||
CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .
|
||||
@@ -57,3 +60,6 @@ release-protos-image:
|
||||
|
||||
snifftest:
|
||||
./hack/snifftest/snifftest.sh
|
||||
|
||||
test-release:
|
||||
goreleaser release --rm-dist --skip-publish --snapshot
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
---
|
||||
|
||||
<center>
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/trufflesecurity/trufflehog/actions)
|
||||
[](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)
|
||||
[](/LICENSE)
|
||||
[](/pkg/detectors) <!-- Badge must be run from staging, see badges/shields#5967 -->
|
||||
[](/pkg/detectors)
|
||||
|
||||
</center>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
@@ -28,27 +28,108 @@ https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkii
|
||||

|
||||
|
||||
```bash
|
||||
docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
||||
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Scan a repo for only verified secrets
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
trufflehog git https://github.com/trufflesecurity/test_keys --only-verified
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
|
||||
|
||||
Found verified result 🐷🔑
|
||||
Detector Type: AWS
|
||||
Decoder Type: PLAIN
|
||||
Raw result: AKIAYVP4CIPPERUVIFXG
|
||||
Line: 4
|
||||
Commit: fbc14303ffbf8fb1c2c1914e8dda7d0121633aca
|
||||
File: keys
|
||||
Email: counter <counter@counters-MacBook-Air.local>
|
||||
Repository: https://github.com/trufflesecurity/test_keys
|
||||
Timestamp: 2022-06-16 10:17:40 -0700 PDT
|
||||
...
|
||||
```
|
||||
|
||||
### Example 2: Scan a GitHub Org for only verified secrets
|
||||
|
||||
```bash
|
||||
trufflehog github --org=trufflesecurity --only-verified
|
||||
```
|
||||
|
||||
### Example 3: Scan a GitHub Repo for only verified keys and get JSON output
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
trufflehog git https://github.com/trufflesecurity/test_keys --only-verified --json
|
||||
```
|
||||
|
||||
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}
|
||||
...
|
||||
```
|
||||
|
||||
### Example 4: Scan an S3 bucket for verified keys
|
||||
|
||||
```bash
|
||||
trufflehog s3 --bucket=<bucket name> --only-verified
|
||||
```
|
||||
|
||||
### Example 5: Scan a Github Repo using SSH authentication in docker
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$HOME/.ssh:/root/.ssh:ro" trufflesecurity/trufflehog:latest git ssh://github.com/trufflesecurity/test_keys
|
||||
```
|
||||
|
||||
### Example 6: Scan individual files or directories
|
||||
|
||||
```bash
|
||||
trufflehog filesystem path/to/file1.txt path/to/file2.txt path/to/dir
|
||||
```
|
||||
|
||||
### Example 7: Scan GCS buckets for verified secrets.
|
||||
|
||||
```bash
|
||||
trufflehog gcs --project-id=<project-ID> --cloud-environment --only-verified
|
||||
```
|
||||
|
||||
### 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 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/)
|
||||
|
||||
|
||||
# What's new in v3?
|
||||
|
||||
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 also added native **support for scanning GitHub, GitLab, filesystems, and S3**.
|
||||
- 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.
|
||||
|
||||
|
||||
## What is credential verification?
|
||||
For every potential credential that is detected, we've painstakingly implemented programatic 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.
|
||||
|
||||
## Installation
|
||||
|
||||
Several options:
|
||||
|
||||
### 1. Go
|
||||
```
|
||||
```bash
|
||||
git clone https://github.com/trufflesecurity/trufflehog.git
|
||||
|
||||
cd trufflehog; go install
|
||||
@@ -59,21 +140,21 @@ cd trufflehog; go install
|
||||
### 3. Docker
|
||||
|
||||
|
||||
> Note: Apple M1 hardware users should run with `docker run --platform linux/arm64` for better performance.
|
||||
> Note: Apple M1 hardware users should run with `docker run --rm --platform linux/arm64` for better performance.
|
||||
|
||||
#### **Most users**
|
||||
|
||||
```bash
|
||||
docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
|
||||
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 avilable, but there is not container image for that.
|
||||
Even better is running the native darwin binary available, but there is no container image for that.
|
||||
|
||||
```bash
|
||||
docker run --platform linux/arm64 -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys
|
||||
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)
|
||||
@@ -87,8 +168,7 @@ Help with setting up this packaging would be appreciated!
|
||||
### 5. Brew
|
||||
|
||||
```bash
|
||||
brew tap trufflesecurity/trufflehog
|
||||
brew install trufflehog
|
||||
brew install trufflesecurity/trufflehog/trufflehog
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -99,11 +179,13 @@ TruffleHog has a sub-command for each source of data that you may want to scan:
|
||||
- github
|
||||
- gitlab
|
||||
- S3
|
||||
- filesystem
|
||||
- filesystem (files and directories)
|
||||
- syslog
|
||||
- file and stdin (coming soon)
|
||||
- circleci
|
||||
- GCS (Google Cloud Storage)
|
||||
- stdin (coming soon)
|
||||
|
||||
Each subcommand can have options that you can see with the `-h` flag provided to the sub command:
|
||||
Each subcommand can have options that you can see with the `--help` flag provided to the sub command:
|
||||
|
||||
```
|
||||
$ trufflehog git --help
|
||||
@@ -113,29 +195,23 @@ Find credentials in git repositories.
|
||||
|
||||
Flags:
|
||||
--help Show context-sensitive help (also try --help-long and --help-man).
|
||||
--debug Run in debug mode
|
||||
--version Prints trufflehog version.
|
||||
--debug Run in debug mode.
|
||||
--trace Run in trace mode.
|
||||
--profile Enables profiling and sets a pprof and fgprof server on :18066.
|
||||
-j, --json Output in JSON format.
|
||||
--json-legacy Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.
|
||||
--concurrency=1 Number of concurrent workers.
|
||||
--concurrency=10 Number of concurrent workers.
|
||||
--no-verification Don't verify the 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.
|
||||
--config=CONFIG Path to configuration file.
|
||||
--print-avg-detector-time Print the average time spent on each detector.
|
||||
--no-update Don't check for updates.
|
||||
-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.
|
||||
--since-commit=SINCE-COMMIT
|
||||
Commit to start scan from.
|
||||
--branch=BRANCH Branch to scan.
|
||||
--max-depth=MAX-DEPTH Maximum depth of commits to scan.
|
||||
--allow No-op flag for backwards compat.
|
||||
--entropy No-op flag for backwards compat.
|
||||
--regex No-op flag for backwards compat.
|
||||
--fail Exit with code 183 if results are found.
|
||||
--version Show application version.
|
||||
|
||||
Args:
|
||||
<uri> Git repository URL. https:// or file:// schema expected.
|
||||
<uri> Git repository URL. https://, file://, or ssh:// schema expected.
|
||||
```
|
||||
|
||||
For example, to scan a `git` repository, start with
|
||||
@@ -154,7 +230,7 @@ Exit Codes:
|
||||
Try scanning an entire GitHub organization with the following:
|
||||
|
||||
```bash
|
||||
docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
||||
docker run --rm -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
|
||||
```
|
||||
|
||||
### TruffleHog OSS Github Action
|
||||
@@ -164,11 +240,13 @@ docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=tru
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
with:
|
||||
# Repository path
|
||||
path:
|
||||
path:
|
||||
# Start scanning from here (usually main branch).
|
||||
base:
|
||||
base:
|
||||
# Scan commits until here (usually dev branch).
|
||||
head: # optional
|
||||
# Extra args to be passed to the trufflehog cli.
|
||||
extra_args: --debug --only-verified
|
||||
```
|
||||
|
||||
The TruffleHog OSS Github Action can be used to scan a range of commits for leaked credentials. The action will fail if
|
||||
@@ -187,13 +265,131 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: TruffleHog OSS
|
||||
uses: trufflesecurity/trufflehog@v3.4.3
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
head: HEAD
|
||||
extra_args: --debug --only-verified
|
||||
```
|
||||
|
||||
### Precommit Hook
|
||||
|
||||
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).
|
||||
|
||||
```yaml
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: trufflehog
|
||||
name: TruffleHog
|
||||
description: Detect secrets in your data.
|
||||
entry: bash -c 'trufflehog git file://. --since-commit HEAD --only-verified --fail'
|
||||
# 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'
|
||||
language: system
|
||||
stages: ["commit", "push"]
|
||||
```
|
||||
|
||||
## Regex Detector (alpha)
|
||||
|
||||
Trufflehog supports detection and verification of custom regular expressions.
|
||||
For detection, at least one **regular expression** and **keyword** is required.
|
||||
A **keyword** is a fixed literal string identifier that appears in or around
|
||||
the regex to be detected. To allow maximum flexibility for verification, a
|
||||
webhook is used containing the regular expression matches.
|
||||
|
||||
Trufflehog will send a JSON POST request containing the regex matches to a
|
||||
configured webhook endpoint. If the endpoint responds with a `200 OK` response
|
||||
status code, the secret is considered verified.
|
||||
|
||||
**NB:** This feature is alpha and subject to change.
|
||||
|
||||
### Regex Detector Example
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
detectors:
|
||||
- name: hog detector
|
||||
keywords:
|
||||
- hog
|
||||
regex:
|
||||
adjective: hogs are (\S+)
|
||||
verify:
|
||||
- endpoint: http://localhost:8000/
|
||||
# unsafe must be set if the endpoint is HTTP
|
||||
unsafe: true
|
||||
headers:
|
||||
- 'Authorization: super secret authorization header'
|
||||
```
|
||||
|
||||
```
|
||||
$ trufflehog filesystem /tmp --config config.yaml --only-verified
|
||||
🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
|
||||
|
||||
Found verified result 🐷🔑
|
||||
Detector Type: CustomRegex
|
||||
Decoder Type: PLAIN
|
||||
Raw result: hogs are cool
|
||||
File: /tmp/hog-facts.txt
|
||||
```
|
||||
|
||||
#### Verification Server Example (Python)
|
||||
|
||||
Unless you run a verification server, secrets found by the custom regex
|
||||
detector will be unverified. Here is an example Python implementation of a
|
||||
verification server for the above `config.yaml` file.
|
||||
|
||||
```python
|
||||
import json
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
AUTH_HEADER = 'super secret authorization header'
|
||||
|
||||
|
||||
class Verifier(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(405)
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
try:
|
||||
if self.headers['Authorization'] != AUTH_HEADER:
|
||||
self.send_response(401)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
# read the body
|
||||
length = int(self.headers['Content-Length'])
|
||||
request = json.loads(self.rfile.read(length))
|
||||
self.log_message("%s", request)
|
||||
|
||||
# check the match
|
||||
if request['hog detector']['adjective'][-1] == 'cool':
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
else:
|
||||
# any other response besides 200
|
||||
self.send_response(406)
|
||||
self.end_headers()
|
||||
except Exception:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
|
||||
|
||||
with HTTPServer(('', 8000), Verifier) as server:
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
```
|
||||
|
||||
## 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)].
|
||||
@@ -217,3 +413,10 @@ We have published some [documentation and tooling to get started on adding new s
|
||||
## 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.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
+8
-1
@@ -8,10 +8,15 @@ inputs:
|
||||
required: true
|
||||
base:
|
||||
description: Start scanning from here (usually main branch).
|
||||
required: true
|
||||
required: false
|
||||
default: ''
|
||||
head:
|
||||
description: Scan commits until here (usually dev branch).
|
||||
required: false
|
||||
extra_args:
|
||||
default: ''
|
||||
description: Extra args to be passed to the trufflehog cli.
|
||||
required: false
|
||||
branding:
|
||||
icon: "shield"
|
||||
color: "green"
|
||||
@@ -26,3 +31,5 @@ runs:
|
||||
- --branch
|
||||
- ${{ inputs.head }}
|
||||
- --fail
|
||||
- --no-update
|
||||
- ${{ inputs.extra_args }}
|
||||
|
||||
Regular → Executable
+5
-3
@@ -1,4 +1,6 @@
|
||||
#! /usr/bin/env bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
args=("$@")
|
||||
/usr/bin/trufflehog ${args[@]}
|
||||
# Parse the last argument into an array of extra_args.
|
||||
mapfile -t extra_args < <(bash -c "for arg in ${*: -1}; do echo \$arg; done")
|
||||
|
||||
/usr/bin/trufflehog "${@: 1: $#-1}" "${extra_args[@]}"
|
||||
@@ -4,57 +4,74 @@ go 1.18
|
||||
|
||||
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-custom7
|
||||
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 (
|
||||
cloud.google.com/go/secretmanager v1.5.0
|
||||
cloud.google.com/go/secretmanager v1.10.0
|
||||
cloud.google.com/go/storage v1.29.0
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
|
||||
github.com/aws/aws-sdk-go v1.44.56
|
||||
github.com/TheZeroSlave/zapsentry v1.14.0
|
||||
github.com/aws/aws-sdk-go v1.44.83
|
||||
github.com/bill-rich/disk-buffer-reader v0.1.7
|
||||
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
|
||||
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.1.0
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.2.0
|
||||
github.com/crewjam/rfc5424 v0.1.0
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/felixge/fgprof v0.9.2
|
||||
github.com/gitleaks/go-gitdiff v0.7.4
|
||||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
github.com/envoyproxy/protoc-gen-validate v0.9.1
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/felixge/fgprof v0.9.3
|
||||
github.com/getsentry/sentry-go v0.19.0
|
||||
github.com/go-errors/errors v1.4.2
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.4
|
||||
github.com/go-logr/logr v1.2.3
|
||||
github.com/go-logr/zapr v1.2.3
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-github/v42 v42.0.0
|
||||
github.com/googleapis/gax-go/v2 v2.7.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2
|
||||
github.com/jlaffaye/ftp v0.1.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/jpillora/overseer v1.1.6
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||
github.com/paulbellamy/ratecounter v0.2.0
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/razorpay/razorpay-go v0.0.0-20210728161131-0341409a6ab2
|
||||
github.com/rs/zerolog v1.27.0
|
||||
github.com/sergi/go-diff v1.2.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/rabbitmq/amqp091-go v1.7.0
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||
github.com/xanzy/go-gitlab v0.69.0
|
||||
github.com/zricethezav/gitleaks/v8 v8.5.2
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad
|
||||
google.golang.org/protobuf v1.28.0
|
||||
github.com/xanzy/go-gitlab v0.81.0
|
||||
go.mongodb.org/mongo-driver v1.11.2
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/exp v0.0.0-20221018205818-5c77f4b2bbd7
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/text v0.8.0
|
||||
google.golang.org/api v0.111.0
|
||||
google.golang.org/protobuf v1.29.1
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.102.0 // indirect
|
||||
cloud.google.com/go/compute v1.7.0 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/compute v1.18.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v0.12.0 // 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/adal v0.9.18 // indirect
|
||||
@@ -62,49 +79,78 @@ require (
|
||||
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/tracing v0.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.1 // 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/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-github/v45 v45.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-github/v50 v50.1.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // 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/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jpillora/s3 v1.1.4 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.23.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/mod v0.5.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/api v0.85.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488 // indirect
|
||||
google.golang.org/grpc v1.53.0 // 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
|
||||
)
|
||||
|
||||
@@ -123,3 +123,7 @@ Do not embed test credentials in the test code. Instead, use GCP Secrets Manager
|
||||
```bash
|
||||
make protos
|
||||
```
|
||||
### Testing a detector
|
||||
```bash
|
||||
go test ./pkg/detectors/<detector> -tags=detectors
|
||||
```
|
||||
|
||||
@@ -49,8 +49,12 @@ If you think that something should be included outside of these guidelines, plea
|
||||
```bash
|
||||
go run hack/generate/generate.go detector <DetectorType enum name>
|
||||
```
|
||||
3. Add Secret Scanner
|
||||
|
||||
3. Complete the secret detector.
|
||||
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{},`
|
||||
|
||||
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 secret detector can be completed with these general steps:
|
||||
@@ -100,3 +104,8 @@ Now, the detector test should attempt to load the given env key from that file.
|
||||
```bash
|
||||
make protos
|
||||
```
|
||||
|
||||
### Testing a detector
|
||||
```bash
|
||||
go test ./pkg/detectors/<detector> -tags=detectors
|
||||
```
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,6 +9,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
@@ -25,7 +26,7 @@ func main() {
|
||||
log.SetPrefix("😲 [generate] ")
|
||||
|
||||
kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||
nameTitle = strings.Title(*name)
|
||||
nameTitle = cases.Title(language.AmericanEnglish).String(*name)
|
||||
nameLower = strings.ToLower(*name)
|
||||
nameUpper = strings.ToUpper(*name)
|
||||
|
||||
@@ -66,7 +67,7 @@ type templateJob struct {
|
||||
}
|
||||
|
||||
func mustWriteTemplates(jobs []templateJob) {
|
||||
log.Printf("Generating %s %s\n", strings.Title(*kind), nameTitle)
|
||||
log.Printf("Generating %s %s\n", cases.Title(language.AmericanEnglish).String(*kind), nameTitle)
|
||||
|
||||
// Make the folder.
|
||||
log.Printf("Creating folder %s\n", folderPath())
|
||||
@@ -77,7 +78,7 @@ func mustWriteTemplates(jobs []templateJob) {
|
||||
|
||||
// Write the files from templates.
|
||||
for _, job := range jobs {
|
||||
tmplBytes, err := ioutil.ReadFile(job.TemplatePath)
|
||||
tmplBytes, err := os.ReadFile(job.TemplatePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -85,7 +86,7 @@ func mustWriteTemplates(jobs []templateJob) {
|
||||
|
||||
for _, rplString := range job.ReplaceString {
|
||||
tmplRaw = strings.ReplaceAll(tmplRaw, strings.ToLower(rplString), "<<.NameLower>>")
|
||||
tmplRaw = strings.ReplaceAll(tmplRaw, strings.Title(rplString), "<<.NameTitle>>")
|
||||
tmplRaw = strings.ReplaceAll(tmplRaw, cases.Title(language.AmericanEnglish).String(rplString), "<<.NameTitle>>")
|
||||
tmplRaw = strings.ReplaceAll(tmplRaw, strings.ToUpper(rplString), "<<.NameUpper>>")
|
||||
}
|
||||
|
||||
|
||||
+39
-20
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -12,13 +11,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/paulbellamy/ratecounter"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/engine"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
@@ -42,6 +42,20 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// setup logger
|
||||
logger, flush := log.New("trufflehog", log.WithConsoleSink(os.Stderr))
|
||||
// make it the default logger for contexts
|
||||
context.SetDefaultLogger(logger)
|
||||
defer func() { _ = flush() }()
|
||||
logFatal := func(err error, message string, keyAndVals ...any) {
|
||||
logger.Error(err, message, keyAndVals...)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Hour*2)
|
||||
var cancelOnce sync.Once
|
||||
defer cancelOnce.Do(cancel)
|
||||
@@ -68,19 +82,19 @@ func main() {
|
||||
} else {
|
||||
_, ok := allScanners[input]
|
||||
if !ok {
|
||||
log.Fatal("could not find scanner by that name")
|
||||
logFatal(fmt.Errorf("invalid input"), "could not find scanner by that name")
|
||||
}
|
||||
selectedScanners[input] = allScanners[input]
|
||||
}
|
||||
if len(selectedScanners) == 0 {
|
||||
log.Fatal("no detectors selected")
|
||||
logFatal(fmt.Errorf("invalid input"), "no detectors selected")
|
||||
}
|
||||
|
||||
for _, excluded := range *scanCmdExclude {
|
||||
delete(selectedScanners, excluded)
|
||||
}
|
||||
|
||||
log.Infof("loaded %d secret detectors", len(selectedScanners)+3)
|
||||
logger.Info("loaded secret detectors", "count", len(selectedScanners)+3)
|
||||
|
||||
var wgScanners sync.WaitGroup
|
||||
|
||||
@@ -92,7 +106,7 @@ func main() {
|
||||
time.Sleep(60 * time.Second)
|
||||
counter.Incr(int64(chunkCounter - prev))
|
||||
prev = chunkCounter
|
||||
log.Infof("chunk scan rate: %d/sec", counter.Rate()/60)
|
||||
logger.Info("chunk scan rate per second", "rate", counter.Rate()/60)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -122,7 +136,7 @@ func main() {
|
||||
|
||||
res, err := scanner.FromData(ctx, *scanVerify, decoded.Data)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logFatal(err, "error scanning chunk")
|
||||
}
|
||||
if len(res) > 0 {
|
||||
if resCounter[name] == nil {
|
||||
@@ -131,16 +145,19 @@ func main() {
|
||||
}
|
||||
atomic.AddUint64(resCounter[name], uint64(len(res)))
|
||||
if *scanThreshold != 0 && int(*resCounter[name]) > *scanThreshold {
|
||||
log.WithField("scanner", name).Errorf("exceeded result threshold of %d", *scanThreshold)
|
||||
logger.Error(
|
||||
fmt.Errorf("exceeded result threshold"), "snifftest failed",
|
||||
"scanner", name, "threshold", *scanThreshold,
|
||||
)
|
||||
failed = true
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *scanPrintRes {
|
||||
for _, r := range res {
|
||||
logger := log.WithField("secret", name).WithField("meta", chunk.SourceMetadata).WithField("result", string(r.Raw))
|
||||
logger := logger.WithValues("secret", name, "meta", chunk.SourceMetadata, "result", string(r.Raw))
|
||||
if *scanPrintChunkRes {
|
||||
logger = logger.WithField("chunk", string(decoded.Data))
|
||||
logger = logger.WithValues("chunk", string(decoded.Data))
|
||||
}
|
||||
logger.Info("result")
|
||||
}
|
||||
@@ -156,18 +173,20 @@ func main() {
|
||||
}
|
||||
|
||||
for _, repo := range strings.Split(*scanCmdRepo, ",") {
|
||||
sem.Acquire(ctx, 1)
|
||||
if err := sem.Acquire(ctx, 1); err != nil {
|
||||
logFatal(err, "timed out waiting for semaphore")
|
||||
}
|
||||
wgChunkers.Add(1)
|
||||
go func(r string) {
|
||||
defer sem.Release(1)
|
||||
defer wgChunkers.Done()
|
||||
log.Infof("cloning %s", r)
|
||||
path, repo, err := git.CloneRepoUsingUnauthenticated(r)
|
||||
logger.Info("cloning repo", "repo", r)
|
||||
path, repo, err := git.CloneRepoUsingUnauthenticated(ctx, r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logFatal(err, "error cloning repo", "repo", r)
|
||||
}
|
||||
|
||||
log.Infof("cloned %s", r)
|
||||
logger.Info("cloned repo", "repo", r)
|
||||
|
||||
s := git.NewGit(sourcespb.SourceType_SOURCE_TYPE_GIT, 0, 0, "snifftest", false, runtime.NumCPU(),
|
||||
func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData {
|
||||
@@ -184,12 +203,12 @@ func main() {
|
||||
}
|
||||
})
|
||||
|
||||
log.Infof("scanning %s", r)
|
||||
logger.Info("scanning repo", "repo", r)
|
||||
err = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), chunksChan)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logFatal(err, "error scanning repo")
|
||||
}
|
||||
log.Infof("scanned %s", r)
|
||||
logger.Info("scanned repo", "repo", r)
|
||||
defer os.RemoveAll(path)
|
||||
}(repo)
|
||||
}
|
||||
@@ -201,9 +220,9 @@ func main() {
|
||||
|
||||
wgScanners.Wait()
|
||||
|
||||
log.WithField("chunks", chunkCounter).Info("completed")
|
||||
logger.Info("completed snifftest", "chunks", chunkCounter)
|
||||
for scanner, resultsCount := range resCounter {
|
||||
log.WithField("results", *resultsCount).Info(scanner)
|
||||
logger.Info(scanner, "results", *resultsCount)
|
||||
}
|
||||
|
||||
if failed {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
@@ -14,37 +12,52 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/felixge/fgprof"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jpillora/overseer"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/updater"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/version"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/engine"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/output"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/updater"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
cli = kingpin.New("TruffleHog", "TruffleHog is a tool for finding credentials.")
|
||||
cmd string
|
||||
debug = cli.Flag("debug", "Run in debug mode.").Bool()
|
||||
trace = cli.Flag("trace", "Run in trace mode.").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()
|
||||
concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
|
||||
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
|
||||
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Bool()
|
||||
cli = kingpin.New("TruffleHog", "TruffleHog is a tool for finding credentials.")
|
||||
cmd string
|
||||
debug = cli.Flag("debug", "Run in debug 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()
|
||||
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()
|
||||
concurrency = cli.Flag("concurrency", "Number of concurrent workers.").Default(strconv.Itoa(runtime.NumCPU())).Int()
|
||||
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
|
||||
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Bool()
|
||||
filterUnverified = cli.Flag("filter-unverified", "Only output first unverified result per chunk per detector if there are more than one results.").Bool()
|
||||
configFilename = cli.Flag("config", "Path to configuration file.").ExistingFile()
|
||||
// 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()
|
||||
noUpdate = cli.Flag("no-update", "Don't check for updates.").Bool()
|
||||
fail = cli.Flag("fail", "Exit with code 183 if results are found.").Bool()
|
||||
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()
|
||||
archiveTimeout = cli.Flag("archive-timeout", "Maximum time to spend extracting an archive.").Duration()
|
||||
includeDetectors = cli.Flag("include-detectors", "Comma separated list of detector types to include. Protobuf name or IDs may be used, as well as ranges.").Default("all").String()
|
||||
excludeDetectors = cli.Flag("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.").String()
|
||||
|
||||
gitScan = cli.Command("git", "Find credentials in git repositories.")
|
||||
gitScanURI = gitScan.Arg("uri", "Git repository URL. https:// or file:// 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()
|
||||
gitScanExcludePaths = gitScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
||||
gitScanSinceCommit = gitScan.Flag("since-commit", "Commit to start scan from.").String()
|
||||
@@ -54,39 +67,62 @@ var (
|
||||
_ = gitScan.Flag("entropy", "No-op flag for backwards compat.").Bool()
|
||||
_ = gitScan.Flag("regex", "No-op flag for backwards compat.").Bool()
|
||||
|
||||
githubScan = cli.Command("github", "Find credentials in GitHub repositories.")
|
||||
githubScanEndpoint = githubScan.Flag("endpoint", "GitHub endpoint.").Default("https://api.github.com").String()
|
||||
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()
|
||||
githubScanToken = githubScan.Flag("token", "GitHub token.").String()
|
||||
githubIncludeForks = githubScan.Flag("include-forks", "Include forks in scan.").Bool()
|
||||
githubIncludeMembers = githubScan.Flag("include-members", "Include organization member repositories in scan.").Bool()
|
||||
githubScan = cli.Command("github", "Find credentials in GitHub repositories.")
|
||||
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()
|
||||
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()
|
||||
githubIncludeForks = githubScan.Flag("include-forks", "Include forks 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()
|
||||
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()
|
||||
githubScanExcludePaths = githubScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
||||
|
||||
gitlabScan = cli.Command("gitlab", "Find credentials in GitLab repositories.")
|
||||
// TODO: Add more GitLab options
|
||||
gitlabScanEndpoint = gitlabScan.Flag("endpoint", "GitLab endpoint.").Default("https://gitlab.com").String()
|
||||
gitlabScanRepos = gitlabScan.Flag("repo", "GitLab repo url. You can repeat this flag. Leave empty to scan all repos accessible with provided credential. Example: https://gitlab.com/org/repo.git").Strings()
|
||||
gitlabScanToken = gitlabScan.Flag("token", "GitLab token.").Required().String()
|
||||
gitlabScanEndpoint = gitlabScan.Flag("endpoint", "GitLab endpoint.").Default("https://gitlab.com").String()
|
||||
gitlabScanRepos = gitlabScan.Flag("repo", "GitLab repo url. You can repeat this flag. Leave empty to scan all repos accessible with provided credential. Example: https://gitlab.com/org/repo.git").Strings()
|
||||
gitlabScanToken = gitlabScan.Flag("token", "GitLab token. Can be provided with environment variable GITLAB_TOKEN.").Envar("GITLAB_TOKEN").Required().String()
|
||||
gitlabScanIncludePaths = gitlabScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
|
||||
gitlabScanExcludePaths = gitlabScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
|
||||
|
||||
filesystemScan = cli.Command("filesystem", "Find credentials in a filesystem.")
|
||||
filesystemDirectories = filesystemScan.Flag("directory", "Path to directory to scan. You can repeat this flag.").Required().Strings()
|
||||
filesystemScan = cli.Command("filesystem", "Find credentials in a filesystem.")
|
||||
filesystemPaths = filesystemScan.Arg("path", "Path to file or directory to scan.").Strings()
|
||||
// DEPRECATED: --directory is deprecated in favor of arguments.
|
||||
filesystemDirectories = filesystemScan.Flag("directory", "Path to directory to scan. You can repeat this flag.").Strings()
|
||||
// TODO: Add more filesystem scan options. Currently only supports scanning a list of directories.
|
||||
// filesystemScanRecursive = filesystemScan.Flag("recursive", "Scan recursively.").Short('r').Bool()
|
||||
// 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()
|
||||
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()
|
||||
|
||||
s3Scan = cli.Command("s3", "Find credentials in S3 buckets.")
|
||||
s3ScanKey = s3Scan.Flag("key", "S3 key used to authenticate.").String()
|
||||
s3ScanSecret = s3Scan.Flag("secret", "S3 secret used to authenticate.").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()
|
||||
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()
|
||||
s3ScanBuckets = s3Scan.Flag("bucket", "Name of S3 bucket to scan. You can repeat this flag.").Strings()
|
||||
|
||||
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 seperated 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")
|
||||
syslogAddress = syslogScan.Flag("address", "Address and port to listen on for syslog. Example: 127.0.0.1:514").String()
|
||||
syslogProtocol = syslogScan.Flag("protocol", "Protocol to listen on. udp or tcp").String()
|
||||
syslogTLSCert = syslogScan.Flag("cert", "Path to TLS cert.").String()
|
||||
syslogTLSKey = syslogScan.Flag("key", "Path to TLS key.").String()
|
||||
syslogFormat = syslogScan.Flag("format", "Log format. Can be rfc3164 or rfc5424").String()
|
||||
|
||||
circleCiScan = cli.Command("circleci", "Scan CircleCI")
|
||||
circleCiScanToken = circleCiScan.Flag("token", "CircleCI token. Can also be provided with environment variable").Envar("CIRCLECI_TOKEN").Required().String()
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -101,22 +137,26 @@ func init() {
|
||||
cli.Version("trufflehog " + version.BuildVersion)
|
||||
cmd = kingpin.MustParse(cli.Parse(os.Args[1:]))
|
||||
|
||||
if *jsonOut {
|
||||
logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||
}
|
||||
switch {
|
||||
case *trace:
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
logrus.Debugf("running version %s", version.BuildVersion)
|
||||
log.SetLevel(5)
|
||||
case *debug:
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.Debugf("running version %s", version.BuildVersion)
|
||||
default:
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
log.SetLevel(2)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// setup logger
|
||||
logFormat := log.WithConsoleSink
|
||||
if *jsonOut {
|
||||
logFormat = log.WithJSONSink
|
||||
}
|
||||
logger, sync := log.New("trufflehog", logFormat(os.Stderr))
|
||||
// make it the default logger for contexts
|
||||
context.SetDefaultLogger(logger)
|
||||
defer func() { _ = sync() }()
|
||||
logFatal := logFatalFunc(logger)
|
||||
|
||||
updateCfg := overseer.Config{
|
||||
Program: run,
|
||||
Debug: *debug,
|
||||
@@ -134,14 +174,16 @@ func main() {
|
||||
|
||||
err := overseer.RunErr(updateCfg)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("error occured with trufflehog updater 🐷")
|
||||
logFatal(err, "error occured with trufflehog updater 🐷")
|
||||
}
|
||||
}
|
||||
|
||||
func run(state overseer.State) {
|
||||
if *debug {
|
||||
fmt.Println("trufflehog " + version.BuildVersion)
|
||||
}
|
||||
ctx := context.Background()
|
||||
logger := ctx.Logger()
|
||||
logFatal := logFatalFunc(logger)
|
||||
|
||||
logger.V(2).Info(fmt.Sprintf("trufflehog %s", version.BuildVersion))
|
||||
|
||||
if *githubScanToken != "" {
|
||||
// NOTE: this kludge is here to do an authenticated shallow commit
|
||||
@@ -154,76 +196,225 @@ func run(state overseer.State) {
|
||||
*concurrency = 1
|
||||
}
|
||||
|
||||
if *debug {
|
||||
if *profile {
|
||||
go func() {
|
||||
router := mux.NewRouter()
|
||||
router.PathPrefix("/debug/pprof").Handler(http.DefaultServeMux)
|
||||
router.PathPrefix("/debug/fgprof").Handler(fgprof.Handler())
|
||||
logrus.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 {
|
||||
logrus.Error(err)
|
||||
logger.Error(err, "error serving pprof and fgprof")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
conf := &config.Config{}
|
||||
if *configFilename != "" {
|
||||
var err error
|
||||
conf, err = config.Read(*configFilename)
|
||||
if err != nil {
|
||||
logFatal(err, "error parsing the provided configuration file")
|
||||
}
|
||||
}
|
||||
|
||||
if *archiveMaxSize != 0 {
|
||||
handlers.SetArchiveMaxSize(int(*archiveMaxSize))
|
||||
}
|
||||
if *archiveMaxDepth != 0 {
|
||||
handlers.SetArchiveMaxDepth(*archiveMaxDepth)
|
||||
}
|
||||
if *archiveTimeout != 0 {
|
||||
handlers.SetArchiveMaxTimeout(*archiveTimeout)
|
||||
}
|
||||
|
||||
// Build include and exclude detector filter sets.
|
||||
var includeDetectorTypes, excludeDetectorTypes map[detectorspb.DetectorType]config.DetectorID
|
||||
{
|
||||
includeList, err := config.ParseDetectors(*includeDetectors)
|
||||
if err != nil {
|
||||
// Exit if there was an error to inform the user of the misconfiguration.
|
||||
logFatal(err, "invalid include list detector configuration")
|
||||
}
|
||||
excludeList, err := config.ParseDetectors(*excludeDetectors)
|
||||
if err != nil {
|
||||
// Exit if there was an error to inform the user of the misconfiguration.
|
||||
logFatal(err, "invalid exclude list detector configuration")
|
||||
}
|
||||
includeDetectorTypes = detectorTypeToMap(includeList)
|
||||
excludeDetectorTypes = detectorTypeToMap(excludeList)
|
||||
}
|
||||
includeFilter := func(d detectors.Detector) bool {
|
||||
id, ok := includeDetectorTypes[d.Type()]
|
||||
if id.Version == 0 {
|
||||
return ok
|
||||
}
|
||||
versionD, ok := d.(detectors.Versioner)
|
||||
if !ok {
|
||||
// Error: version provided but not a detectors.Versioner
|
||||
logFatal(
|
||||
fmt.Errorf("version provided but detector does not have a version"),
|
||||
"invalid include list detector configuration",
|
||||
"detector", id,
|
||||
)
|
||||
}
|
||||
return versionD.Version() == id.Version
|
||||
}
|
||||
excludeFilter := func(d detectors.Detector) bool {
|
||||
id, ok := excludeDetectorTypes[d.Type()]
|
||||
if id.Version == 0 {
|
||||
return !ok
|
||||
}
|
||||
versionD, ok := d.(detectors.Versioner)
|
||||
if !ok {
|
||||
// Error: version provided but not a detectors.Versioner
|
||||
logFatal(
|
||||
fmt.Errorf("version provided but detector does not have a version"),
|
||||
"invalid exclude list detector configuration",
|
||||
"detector", id,
|
||||
)
|
||||
}
|
||||
return versionD.Version() != id.Version
|
||||
}
|
||||
|
||||
e := engine.Start(ctx,
|
||||
engine.WithConcurrency(*concurrency),
|
||||
engine.WithDecoders(decoders.DefaultDecoders()...),
|
||||
engine.WithDetectors(!*noVerification, engine.DefaultDetectors()...),
|
||||
engine.WithDetectors(!*noVerification, conf.Detectors...),
|
||||
engine.WithFilterDetectors(includeFilter),
|
||||
engine.WithFilterDetectors(excludeFilter),
|
||||
engine.WithFilterUnverified(*filterUnverified),
|
||||
)
|
||||
|
||||
filter, err := common.FilterFromFiles(*gitScanIncludePaths, *gitScanExcludePaths)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("could not create filter")
|
||||
}
|
||||
|
||||
var repoPath string
|
||||
var remote bool
|
||||
switch cmd {
|
||||
case gitScan.FullCommand():
|
||||
repoPath, remote, err = git.PrepareRepoSinceCommit(*gitScanURI, *gitScanSinceCommit)
|
||||
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 == "" {
|
||||
logrus.WithError(err).Fatal("error preparing git repo for scanning")
|
||||
logFatal(err, "error preparing git repo for scanning")
|
||||
}
|
||||
if remote {
|
||||
defer os.RemoveAll(repoPath)
|
||||
}
|
||||
err = e.ScanGit(ctx, repoPath, *gitScanBranch, *gitScanSinceCommit, *gitScanMaxDepth, filter)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to scan git.")
|
||||
|
||||
cfg := sources.GitConfig{
|
||||
RepoPath: repoPath,
|
||||
HeadRef: *gitScanBranch,
|
||||
BaseRef: *gitScanSinceCommit,
|
||||
MaxDepth: *gitScanMaxDepth,
|
||||
Filter: filter,
|
||||
}
|
||||
if err = e.ScanGit(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan Git.")
|
||||
}
|
||||
case githubScan.FullCommand():
|
||||
if len(*githubScanOrgs) == 0 && len(*githubScanRepos) == 0 {
|
||||
log.Fatal("You must specify at least one organization or repository.")
|
||||
}
|
||||
err = e.ScanGitHub(ctx, *githubScanEndpoint, *githubScanRepos, *githubScanOrgs, *githubScanToken, *githubIncludeForks, filter, *concurrency, *githubIncludeMembers)
|
||||
filter, err := common.FilterFromFiles(*githubScanIncludePaths, *githubScanExcludePaths)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to scan git.")
|
||||
logFatal(err, "could not create filter")
|
||||
}
|
||||
if len(*githubScanOrgs) == 0 && len(*githubScanRepos) == 0 {
|
||||
logFatal(fmt.Errorf("invalid config"), "You must specify at least one organization or repository.")
|
||||
}
|
||||
|
||||
cfg := sources.GithubConfig{
|
||||
Endpoint: *githubScanEndpoint,
|
||||
Token: *githubScanToken,
|
||||
IncludeForks: *githubIncludeForks,
|
||||
IncludeMembers: *githubIncludeMembers,
|
||||
Concurrency: *concurrency,
|
||||
ExcludeRepos: *githubExcludeRepos,
|
||||
IncludeRepos: *githubIncludeRepos,
|
||||
Repos: *githubScanRepos,
|
||||
Orgs: *githubScanOrgs,
|
||||
Filter: filter,
|
||||
}
|
||||
if err := e.ScanGitHub(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan Github.")
|
||||
}
|
||||
case gitlabScan.FullCommand():
|
||||
err := e.ScanGitLab(ctx, *gitlabScanEndpoint, *gitlabScanToken, *gitlabScanRepos)
|
||||
filter, err := common.FilterFromFiles(*gitlabScanIncludePaths, *gitlabScanExcludePaths)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to scan GitLab.")
|
||||
logFatal(err, "could not create filter")
|
||||
}
|
||||
|
||||
cfg := sources.GitlabConfig{
|
||||
Endpoint: *gitlabScanEndpoint,
|
||||
Token: *gitlabScanToken,
|
||||
Repos: *gitlabScanRepos,
|
||||
Filter: filter,
|
||||
}
|
||||
if err := e.ScanGitLab(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan GitLab.")
|
||||
}
|
||||
case filesystemScan.FullCommand():
|
||||
err := e.ScanFileSystem(ctx, *filesystemDirectories)
|
||||
filter, err := common.FilterFromFiles(*filesystemScanIncludePaths, *filesystemScanExcludePaths)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to scan filesystem")
|
||||
logFatal(err, "could not create filter")
|
||||
}
|
||||
if len(*filesystemDirectories) > 0 {
|
||||
ctx.Logger().Info("--directory flag is deprecated, please pass directories as arguments")
|
||||
}
|
||||
paths := make([]string, 0, len(*filesystemPaths)+len(*filesystemDirectories))
|
||||
paths = append(paths, *filesystemPaths...)
|
||||
paths = append(paths, *filesystemDirectories...)
|
||||
cfg := sources.FilesystemConfig{
|
||||
Paths: paths,
|
||||
Filter: filter,
|
||||
}
|
||||
if err = e.ScanFileSystem(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan filesystem")
|
||||
}
|
||||
case s3Scan.FullCommand():
|
||||
err := e.ScanS3(ctx, *s3ScanKey, *s3ScanSecret, *s3ScanCloudEnv, *s3ScanBuckets)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to scan S3.")
|
||||
cfg := sources.S3Config{
|
||||
Key: *s3ScanKey,
|
||||
Secret: *s3ScanSecret,
|
||||
Buckets: *s3ScanBuckets,
|
||||
CloudCred: *s3ScanCloudEnv,
|
||||
}
|
||||
if err := e.ScanS3(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan S3.")
|
||||
}
|
||||
case syslogScan.FullCommand():
|
||||
err := e.ScanSyslog(ctx, *syslogAddress, *syslogProtocol, *syslogTLSCert, *syslogTLSKey, *syslogFormat, *concurrency)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to scan syslog.")
|
||||
cfg := sources.SyslogConfig{
|
||||
Address: *syslogAddress,
|
||||
Format: *syslogFormat,
|
||||
Protocol: *syslogProtocol,
|
||||
CertPath: *syslogTLSCert,
|
||||
KeyPath: *syslogTLSKey,
|
||||
Concurrency: *concurrency,
|
||||
}
|
||||
if err := e.ScanSyslog(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan syslog.")
|
||||
}
|
||||
case circleCiScan.FullCommand():
|
||||
if err := e.ScanCircleCI(ctx, *circleCiScanToken); err != nil {
|
||||
logFatal(err, "Failed to scan CircleCI.")
|
||||
}
|
||||
case gcsScan.FullCommand():
|
||||
cfg := sources.GCSConfig{
|
||||
ProjectID: *gcsProjectID,
|
||||
CloudCred: *gcsCloudEnv,
|
||||
ServiceAccount: *gcsServiceAccount,
|
||||
WithoutAuth: *gcsWithoutAuth,
|
||||
ApiKey: *gcsAPIKey,
|
||||
IncludeBuckets: commaSeperatedToSlice(*gcsIncludeBuckets),
|
||||
ExcludeBuckets: commaSeperatedToSlice(*gcsExcludeBuckets),
|
||||
IncludeObjects: commaSeperatedToSlice(*gcsIncludeObjects),
|
||||
ExcludeObjects: commaSeperatedToSlice(*gcsExcludeObjects),
|
||||
Concurrency: *concurrency,
|
||||
MaxObjectSize: int64(*gcsMaxObjectSize),
|
||||
}
|
||||
if err := e.ScanGCS(ctx, cfg); err != nil {
|
||||
logFatal(err, "Failed to scan GCS.")
|
||||
}
|
||||
}
|
||||
// asynchronously wait for scanning to finish and cleanup
|
||||
go e.Finish()
|
||||
go e.Finish(ctx)
|
||||
|
||||
if !*jsonLegacy && !*jsonOut {
|
||||
fmt.Fprintf(os.Stderr, "🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n")
|
||||
@@ -238,27 +429,48 @@ func run(state overseer.State) {
|
||||
}
|
||||
foundResults = true
|
||||
|
||||
var err error
|
||||
switch {
|
||||
case *jsonLegacy:
|
||||
output.PrintLegacyJSON(&r)
|
||||
err = output.PrintLegacyJSON(ctx, &r)
|
||||
case *jsonOut:
|
||||
output.PrintJSON(&r)
|
||||
err = output.PrintJSON(&r)
|
||||
default:
|
||||
output.PrintPlainOutput(&r)
|
||||
err = output.PrintPlainOutput(&r)
|
||||
}
|
||||
if err != nil {
|
||||
logFatal(err, "error printing results")
|
||||
}
|
||||
}
|
||||
logrus.Debugf("scanned %d chunks", e.ChunksScanned())
|
||||
logger.V(2).Info("finished scanning",
|
||||
"chunks", e.ChunksScanned(),
|
||||
"bytes", e.BytesScanned(),
|
||||
)
|
||||
|
||||
if *printAvgDetectorTime {
|
||||
printAverageDetectorTime(e)
|
||||
}
|
||||
|
||||
if foundResults && *fail {
|
||||
logrus.Debug("exiting with code 183 because results were found")
|
||||
logger.V(2).Info("exiting with code 183 because results were found")
|
||||
os.Exit(183)
|
||||
}
|
||||
}
|
||||
|
||||
func commaSeperatedToSlice(s []string) []string {
|
||||
var result []string
|
||||
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, durations := range e.DetectorAvgTime() {
|
||||
@@ -270,3 +482,24 @@ func printAverageDetectorTime(e *engine.Engine) {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", detectorName, avgDuration)
|
||||
}
|
||||
}
|
||||
|
||||
// logFatalFunc returns a log.Fatal style function. Calling the returned
|
||||
// function will terminate the program without cleanup.
|
||||
func logFatalFunc(logger logr.Logger) func(error, string, ...any) {
|
||||
return func(err error, message string, keyAndVals ...any) {
|
||||
logger.Error(err, message, keyAndVals...)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func detectorTypeToMap(detectors []config.DetectorID) map[detectorspb.DetectorType]config.DetectorID {
|
||||
output := make(map[detectorspb.DetectorType]config.DetectorID, len(detectors))
|
||||
for _, d := range detectors {
|
||||
output[d.ID] = d
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
+15
-4
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
@@ -20,7 +20,8 @@ type FilterRuleSet []regexp.Regexp
|
||||
func FilterEmpty() *Filter {
|
||||
filter, err := FilterFromFiles("", "")
|
||||
if err != nil {
|
||||
log.WithError(err).Fatalf("could not create empty filter")
|
||||
context.Background().Logger().Error(err, "could not create empty filter")
|
||||
os.Exit(1)
|
||||
}
|
||||
return filter
|
||||
}
|
||||
@@ -57,15 +58,19 @@ func FilterRulesFromFile(source string) (*FilterRuleSet, error) {
|
||||
}
|
||||
|
||||
commentPattern := regexp.MustCompile(`^\s*#`)
|
||||
emptyLinePattern := regexp.MustCompile(`^\s*$`)
|
||||
|
||||
file, err := os.Open(source)
|
||||
logger := context.Background().Logger().WithValues("file", source)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatalf("unable to open filter file: %s", source)
|
||||
logger.Error(err, "unable to open filter file", "file", source)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatalf("unable to close filter file: %s", source)
|
||||
logger.Error(err, "unable to close filter file")
|
||||
os.Exit(1)
|
||||
}
|
||||
}(file)
|
||||
|
||||
@@ -75,6 +80,9 @@ func FilterRulesFromFile(source string) (*FilterRuleSet, error) {
|
||||
if commentPattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
if emptyLinePattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
pattern, err := regexp.Compile(line)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not compile regular expression: %s", line)
|
||||
@@ -86,6 +94,9 @@ func FilterRulesFromFile(source string) (*FilterRuleSet, error) {
|
||||
|
||||
// Pass returns true if the include FilterRuleSet matches the pattern and the exclude FilterRuleSet does not match.
|
||||
func (filter *Filter) Pass(object string) bool {
|
||||
if filter == nil {
|
||||
return true
|
||||
}
|
||||
excluded := filter.exclude.Matches(object)
|
||||
included := filter.include.Matches(object)
|
||||
return !excluded && included
|
||||
|
||||
@@ -129,6 +129,13 @@ func TestFilterFromFile(t *testing.T) {
|
||||
pattern: "test",
|
||||
pass: false,
|
||||
},
|
||||
"EmptyLinesAreIgnored": {
|
||||
includeFile: false,
|
||||
excludeFile: true,
|
||||
excludeFileContents: " \ntest.txt",
|
||||
pattern: "hello world.txt",
|
||||
pass: true,
|
||||
},
|
||||
}
|
||||
for name, test := range tests {
|
||||
var includeTestFile, excludeTestFile string
|
||||
|
||||
+10
-1
@@ -121,6 +121,15 @@ func RetryableHttpClient() *http.Client {
|
||||
return httpClient.StandardClient()
|
||||
}
|
||||
|
||||
func RetryableHttpClientTimeout(timeOutSeconds int64) *http.Client {
|
||||
httpClient := retryablehttp.NewClient()
|
||||
httpClient.RetryMax = 3
|
||||
httpClient.Logger = nil
|
||||
httpClient.HTTPClient.Timeout = time.Duration(timeOutSeconds) * time.Second
|
||||
httpClient.HTTPClient.Transport = NewCustomTransport(nil)
|
||||
return httpClient.StandardClient()
|
||||
}
|
||||
|
||||
const DefaultResponseTimeout = 5 * time.Second
|
||||
|
||||
var saneTransport = &http.Transport{
|
||||
@@ -142,7 +151,7 @@ func SaneHttpClient() *http.Client {
|
||||
return httpClient
|
||||
}
|
||||
|
||||
//custom timeout for some scanners
|
||||
// SaneHttpClientTimeOut adds a custom timeout for some scanners
|
||||
func SaneHttpClientTimeOut(timeOutSeconds int64) *http.Client {
|
||||
httpClient := &http.Client{}
|
||||
httpClient.Timeout = time.Second * time.Duration(timeOutSeconds)
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const EmailPattern = `\b(?:[a-z0-9!#$%&'*+/=?^_\x60{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_\x60{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])\b`
|
||||
@@ -24,7 +22,7 @@ func BuildRegex(pattern string, specialChar string, length int) string {
|
||||
|
||||
func BuildRegexJWT(firstRange, secondRange, thirdRange string) string {
|
||||
if RangeValidation(firstRange) || RangeValidation(secondRange) || RangeValidation(thirdRange) {
|
||||
log.Error("Min value should not be greater than or equal to max")
|
||||
panic("Min value should not be greater than or equal to max")
|
||||
}
|
||||
return fmt.Sprintf(`\b(ey[%s]{%s}.ey[%s-\/_]{%s}.[%s-\/_]{%s})\b`, AlphaNumPattern, firstRange, AlphaNumPattern, secondRange, AlphaNumPattern, thirdRange)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
// Recover handles panics and reports to Sentry.
|
||||
func Recover(ctx context.Context) {
|
||||
if err := recover(); err != nil {
|
||||
panicStack := string(debug.Stack())
|
||||
if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
|
||||
ctx.Logger().Info("panic captured", "event_id", *eventID)
|
||||
}
|
||||
ctx.Logger().Error(fmt.Errorf("panic"), "recovered from panic",
|
||||
"stack-trace", panicStack,
|
||||
"recover", err,
|
||||
)
|
||||
if !sentry.Flush(time.Second * 5) {
|
||||
ctx.Logger().Info("sentry flush failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RecoverWithExit handles panics and reports to Sentry before exiting.
|
||||
func RecoverWithExit(ctx context.Context) {
|
||||
if err := recover(); err != nil {
|
||||
panicStack := string(debug.Stack())
|
||||
if eventID := sentry.CurrentHub().Recover(err); eventID != nil {
|
||||
ctx.Logger().Info("panic captured", "event_id", *eventID)
|
||||
}
|
||||
ctx.Logger().Error(fmt.Errorf("panic"), "recovered from panic before exiting",
|
||||
"stack-trace", panicStack,
|
||||
"recover", err,
|
||||
)
|
||||
if !sentry.Flush(time.Second * 5) {
|
||||
ctx.Logger().Info("sentry flush failed")
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"time"
|
||||
|
||||
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
||||
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/pkg/errors"
|
||||
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
|
||||
)
|
||||
|
||||
type Secret struct{ kv map[string]string }
|
||||
|
||||
+5
-12
@@ -2,24 +2,17 @@ package common
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/h2non/filetype"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
KB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15
|
||||
IGNORED_EXTENSIONS = []string{"pdf", "mp4", "avi", "mpeg", "mpg", "mov", "wmv", "m4p", "swf", "mp2", "flv", "vob", "webm", "hdv", "3gp", "ogg", "mp3", "wav", "flac", "tif", "tiff", "jpg", "jpeg", "png", "gif", "zip", "webp"}
|
||||
IgnoredExtensions = []string{"mp4", "avi", "mpeg", "mpg", "mov", "wmv", "m4p", "swf", "mp2", "flv", "vob", "webm", "hdv", "3gp", "ogg", "mp3", "wav", "flac", "webp"}
|
||||
)
|
||||
|
||||
func SkipFile(filename string, data []byte) bool {
|
||||
if filepath.Ext(filename) == "" {
|
||||
//no sepcified extension, check mimetype
|
||||
if filetype.IsArchive(data[:256]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, ext := range IGNORED_EXTENSIONS {
|
||||
if filepath.Ext(filename) == ext {
|
||||
func SkipFile(filename string) bool {
|
||||
for _, ext := range IgnoredExtensions {
|
||||
if strings.TrimPrefix(filepath.Ext(filename), ".") == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml"
|
||||
)
|
||||
|
||||
// Config holds user supplied configuration.
|
||||
type Config struct {
|
||||
Detectors []detectors.Detector
|
||||
}
|
||||
|
||||
// Read parses a given filename into a Config.
|
||||
func Read(filename string) (*Config, error) {
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewYAML(input)
|
||||
}
|
||||
|
||||
// NewYAML parses the given YAML data into a Config.
|
||||
func NewYAML(input []byte) (*Config, error) {
|
||||
// Parse the raw YAML into a structure.
|
||||
var messages custom_detectorspb.CustomDetectors
|
||||
if err := protoyaml.UnmarshalStrict(input, &messages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert the structured YAML into detectors.
|
||||
var detectors []detectors.Detector
|
||||
for _, detectorConfig := range messages.Detectors {
|
||||
detector, err := custom_detectors.NewWebhookCustomRegex(detectorConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
detectors = append(detectors, detector)
|
||||
}
|
||||
return &Config{
|
||||
Detectors: detectors,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
dpb "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
var (
|
||||
specialGroups = map[string][]DetectorID{
|
||||
"all": allDetectors(),
|
||||
}
|
||||
|
||||
detectorTypeValue = make(map[string]dpb.DetectorType, len(dpb.DetectorType_value))
|
||||
validDetectors = make(map[dpb.DetectorType]struct{}, len(dpb.DetectorType_value))
|
||||
maxDetectorType dpb.DetectorType
|
||||
)
|
||||
|
||||
// Setup package local global variables.
|
||||
func init() {
|
||||
for k, v := range dpb.DetectorType_value {
|
||||
dt := dpb.DetectorType(v)
|
||||
detectorTypeValue[strings.ToLower(k)] = dt
|
||||
validDetectors[dt] = struct{}{}
|
||||
if dt > maxDetectorType {
|
||||
maxDetectorType = dt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DetectorID struct {
|
||||
ID dpb.DetectorType
|
||||
Version int
|
||||
}
|
||||
|
||||
// ParseDetectors parses user supplied string into a list of detectors types.
|
||||
// "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
|
||||
// protobuf, or the protobuf enum number. A range may be used as well in the
|
||||
// form "start-end". Order is preserved and duplicates are ignored.
|
||||
func ParseDetectors(input string) ([]DetectorID, error) {
|
||||
var output []DetectorID
|
||||
seenDetector := map[DetectorID]struct{}{}
|
||||
for _, item := range strings.Split(input, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
detectors, ok := specialGroups[strings.ToLower(item)]
|
||||
if !ok {
|
||||
var err error
|
||||
detectors, err = asRange(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, d := range detectors {
|
||||
if _, ok := seenDetector[d]; ok {
|
||||
continue
|
||||
}
|
||||
seenDetector[d] = struct{}{}
|
||||
output = append(output, d)
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (id DetectorID) String() string {
|
||||
name := dpb.DetectorType_name[int32(id.ID)]
|
||||
if name == "" {
|
||||
name = "<invalid ID>"
|
||||
}
|
||||
if id.Version == 0 {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf("%s.v%d", name, id.Version)
|
||||
}
|
||||
|
||||
// allDetectors reutrns an ordered slice of all detector types.
|
||||
func allDetectors() []DetectorID {
|
||||
all := make([]DetectorID, 0, len(dpb.DetectorType_name))
|
||||
for id := range dpb.DetectorType_name {
|
||||
all = append(all, DetectorID{ID: dpb.DetectorType(id)})
|
||||
}
|
||||
sort.Slice(all, func(i, j int) bool { return all[i].ID < all[j].ID })
|
||||
return all
|
||||
}
|
||||
|
||||
// asRange converts a single input into a slice of detector types. If the input
|
||||
// is not in range format, a slice of length 1 is returned. Unbounded ranges
|
||||
// are allowed.
|
||||
func asRange(input string) ([]DetectorID, error) {
|
||||
// Check if it's a single detector type.
|
||||
dt, err := asDetectorID(input)
|
||||
if err == nil {
|
||||
return []DetectorID{dt}, nil
|
||||
}
|
||||
|
||||
// Check if it's a range; if not return the error from above.
|
||||
start, end, found := strings.Cut(input, "-")
|
||||
if !found {
|
||||
return nil, err
|
||||
}
|
||||
start, end = strings.TrimSpace(start), strings.TrimSpace(end)
|
||||
|
||||
// Convert the range start and end to a DetectorType.
|
||||
dtStart, err := asDetectorID(start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtEnd, err := asDetectorID(end)
|
||||
// If end is empty it's an unbounded range.
|
||||
if err != nil && end != "" {
|
||||
return nil, err
|
||||
}
|
||||
if end == "" {
|
||||
dtEnd.ID = maxDetectorType
|
||||
}
|
||||
|
||||
// Ensure these ranges don't have versions.
|
||||
if dtEnd.Version != 0 || dtStart.Version != 0 {
|
||||
return nil, fmt.Errorf("versions within ranges are not supported: %s", input)
|
||||
}
|
||||
|
||||
step := dpb.DetectorType(1)
|
||||
if dtStart.ID > dtEnd.ID {
|
||||
step = -1
|
||||
}
|
||||
var output []DetectorID
|
||||
for dt := dtStart.ID; dt != dtEnd.ID; dt += step {
|
||||
if _, ok := validDetectors[dt]; !ok {
|
||||
continue
|
||||
}
|
||||
output = append(output, DetectorID{ID: dt})
|
||||
}
|
||||
return append(output, dtEnd), nil
|
||||
}
|
||||
|
||||
// asDetectorID converts the case-insensitive input into a DetectorID.
|
||||
// Name or ID may be used.
|
||||
func asDetectorID(input string) (DetectorID, error) {
|
||||
if input == "" {
|
||||
return DetectorID{}, fmt.Errorf("empty detector")
|
||||
}
|
||||
var detectorID DetectorID
|
||||
// Separate the version if there is one.
|
||||
if detector, version, hasVersion := strings.Cut(input, "."); hasVersion {
|
||||
parsedVersion, err := parseVersion(version)
|
||||
if err != nil {
|
||||
return DetectorID{}, fmt.Errorf("invalid version for input: %q error: %w", input, err)
|
||||
}
|
||||
detectorID.Version = parsedVersion
|
||||
// Because there was a version, the detector type input is the part before the '.'
|
||||
input = detector
|
||||
}
|
||||
|
||||
// Check if it's a named detector.
|
||||
if dt, ok := detectorTypeValue[strings.ToLower(input)]; ok {
|
||||
detectorID.ID = dt
|
||||
return detectorID, nil
|
||||
}
|
||||
// Check if it's a detector ID.
|
||||
if i, err := strconv.ParseInt(input, 10, 32); err == nil {
|
||||
dt := dpb.DetectorType(i)
|
||||
if _, ok := validDetectors[dt]; !ok {
|
||||
return DetectorID{}, fmt.Errorf("invalid detector ID: %s", input)
|
||||
}
|
||||
detectorID.ID = dt
|
||||
return detectorID, nil
|
||||
}
|
||||
return DetectorID{}, fmt.Errorf("unrecognized detector type: %s", input)
|
||||
}
|
||||
|
||||
func parseVersion(v string) (int, error) {
|
||||
if !strings.HasPrefix(strings.ToLower(v), "v") {
|
||||
return 0, fmt.Errorf("version must start with 'v'")
|
||||
}
|
||||
version := strings.TrimLeft(v, "vV")
|
||||
return strconv.Atoi(version)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
dpb "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
func TestDetectorParsing(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input string
|
||||
expected []DetectorID
|
||||
}{
|
||||
"all": {"AlL", allDetectors()},
|
||||
"trailing range": {"0-", allDetectors()},
|
||||
"all after 1": {"1-", allDetectors()[1:]},
|
||||
"named and valid range": {"aWs,8-9", []DetectorID{{ID: dpb.DetectorType_AWS}, {ID: dpb.DetectorType_Github}, {ID: dpb.DetectorType_Gitlab}}},
|
||||
"duplicate order preserved": {"9, 8, 9", []DetectorID{{ID: 9}, {ID: 8}}},
|
||||
"named range": {"github - gitlab", []DetectorID{{ID: dpb.DetectorType_Github}, {ID: dpb.DetectorType_Gitlab}}},
|
||||
"range preserved": {"8-9, 7-10", []DetectorID{{ID: 8}, {ID: 9}, {ID: 7}, {ID: 10}}},
|
||||
"reverse range": {"9-8", []DetectorID{{ID: 9}, {ID: 8}}},
|
||||
"range preserved with all": {"10-,all", append(allDetectors()[10:], allDetectors()[:10]...)},
|
||||
"empty list item": {"8, ,9", []DetectorID{{ID: 8}, {ID: 9}}},
|
||||
"invalid end range": {"0-1337", nil},
|
||||
"invalid name": {"foo", nil},
|
||||
"negative": {"-1", nil},
|
||||
"github.v1": {"github.v1", []DetectorID{{ID: dpb.DetectorType_Github, Version: 1}}},
|
||||
"gitlab.v100": {"gitlab.v100", []DetectorID{{ID: dpb.DetectorType_Gitlab, Version: 100}}},
|
||||
"range with versions": {"github.v2 - gitlab.v1", nil},
|
||||
"invalid version no v": {"gitlab.2", nil},
|
||||
"invalid version no number": {"gitlab.github", nil},
|
||||
"capital V is fine": {"GiTlAb.V2", []DetectorID{{ID: dpb.DetectorType_Gitlab, Version: 2}}},
|
||||
"id number with version": {"8.v2", []DetectorID{{ID: 8, Version: 2}}},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, gotErr := ParseDetectors(tt.input)
|
||||
if tt.expected == nil {
|
||||
assert.Error(t, gotErr)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
var (
|
||||
// defaultLogger can be set via SetDefaultLogger.
|
||||
defaultLogger logr.Logger = logr.Discard()
|
||||
)
|
||||
|
||||
// Context wraps context.Context and includes an additional Logger() method.
|
||||
type Context interface {
|
||||
context.Context
|
||||
Logger() logr.Logger
|
||||
Parent() context.Context
|
||||
SetParent(ctx context.Context) Context
|
||||
}
|
||||
|
||||
// Parent returns the parent context.
|
||||
func (l logCtx) Parent() context.Context {
|
||||
return l.Context
|
||||
}
|
||||
|
||||
// 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.
|
||||
type logCtx struct {
|
||||
// Embed context.Context to get all methods for free.
|
||||
context.Context
|
||||
log logr.Logger
|
||||
err *error
|
||||
}
|
||||
|
||||
// Logger returns a structured logger.
|
||||
func (l logCtx) Logger() logr.Logger {
|
||||
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.
|
||||
func Background() Context {
|
||||
return logCtx{
|
||||
log: defaultLogger,
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO returns context.TODO with a default logger.
|
||||
func TODO() Context {
|
||||
return logCtx{
|
||||
log: defaultLogger,
|
||||
Context: context.TODO(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithCancel returns context.WithCancel with the log object propagated.
|
||||
func WithCancel(parent Context) (Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
lCtx := logCtx{
|
||||
log: parent.Logger(),
|
||||
Context: ctx,
|
||||
}
|
||||
return captureCancelCallstack(lCtx, cancel)
|
||||
}
|
||||
|
||||
// WithDeadline returns context.WithDeadline with the log object propagated and
|
||||
// the deadline added to the structured log values.
|
||||
func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithDeadline(parent, d)
|
||||
lCtx := logCtx{
|
||||
log: parent.Logger().WithValues("deadline", d),
|
||||
Context: ctx,
|
||||
}
|
||||
return captureCancelCallstack(lCtx, cancel)
|
||||
}
|
||||
|
||||
// WithTimeout returns context.WithTimeout with the log object propagated and
|
||||
// the timeout added to the structured log values.
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithTimeout(parent, timeout)
|
||||
lCtx := logCtx{
|
||||
log: parent.Logger().WithValues("timeout", timeout),
|
||||
Context: ctx,
|
||||
}
|
||||
return captureCancelCallstack(lCtx, cancel)
|
||||
}
|
||||
|
||||
// WithValue returns context.WithValue with the log object propagated and
|
||||
// the value added to the structured log values (if the key is a string).
|
||||
func WithValue(parent Context, key, val any) Context {
|
||||
logger := parent.Logger()
|
||||
if k, ok := key.(string); ok {
|
||||
logger = logger.WithValues(k, val)
|
||||
}
|
||||
return logCtx{
|
||||
log: logger,
|
||||
Context: context.WithValue(parent, key, val),
|
||||
}
|
||||
}
|
||||
|
||||
// WithValues returns context.WithValue with the log object propagated and
|
||||
// the values added to the structured log values (if the key is a string).
|
||||
func WithValues(parent Context, keyAndVals ...any) Context {
|
||||
ctx := parent
|
||||
for i := 0; i < len(keyAndVals)-1; i += 2 {
|
||||
ctx = WithValue(ctx, keyAndVals[i], keyAndVals[i+1])
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithLogger converts a context.Context into a Context by adding a logger.
|
||||
func WithLogger(parent context.Context, logger logr.Logger) Context {
|
||||
return logCtx{
|
||||
log: logger,
|
||||
Context: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// AddLogger converts a context.Context into a Context. If the underlying type
|
||||
// is already a Context, that will be returned, otherwise a default logger will
|
||||
// be added.
|
||||
func AddLogger(parent context.Context) Context {
|
||||
if loggerCtx, ok := parent.(Context); ok {
|
||||
return loggerCtx
|
||||
}
|
||||
return WithLogger(parent, defaultLogger)
|
||||
}
|
||||
|
||||
// SetupDefaultLogger sets the package-level global default logger that will be
|
||||
// used for Background and TODO contexts.
|
||||
func SetDefaultLogger(l logr.Logger) {
|
||||
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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/zapr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
// testLogger is a helper function to create a logger with a closure callback.
|
||||
func testLogger(t *testing.T, f func(zapcore.Entry)) logr.Logger {
|
||||
return zapr.NewLogger(zaptest.NewLogger(t,
|
||||
zaptest.WrapOptions(zap.Hooks(func(e zapcore.Entry) error {
|
||||
f(e)
|
||||
return nil
|
||||
}))))
|
||||
}
|
||||
|
||||
// infoCounterContext is a helper function to create a Context that will count
|
||||
// the number of Info messages logged.
|
||||
func infoCounterContext(t *testing.T) (Context, *int) {
|
||||
var infoCount int
|
||||
logger := testLogger(t, func(e zapcore.Entry) {
|
||||
if e.Level == zap.InfoLevel {
|
||||
infoCount++
|
||||
}
|
||||
})
|
||||
return WithLogger(context.Background(), logger), &infoCount
|
||||
}
|
||||
|
||||
func TestWithCancel(t *testing.T) {
|
||||
parentCtx, infoCount := infoCounterContext(t)
|
||||
ctx, cancel := WithCancel(parentCtx)
|
||||
cancel()
|
||||
assert.Equal(t, 0, *infoCount)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ctx.Logger().Info("yay")
|
||||
case <-time.After(1 * time.Second):
|
||||
assert.Fail(t, "context should be done")
|
||||
}
|
||||
assert.Equal(t, 1, *infoCount)
|
||||
}
|
||||
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
parentCtx, infoCount := infoCounterContext(t)
|
||||
ctx, cancel := WithTimeout(parentCtx, 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
assert.Equal(t, 0, *infoCount)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ctx.Logger().Info("yay")
|
||||
case <-time.After(1 * time.Second):
|
||||
assert.Fail(t, "context should be done")
|
||||
}
|
||||
assert.Equal(t, 1, *infoCount)
|
||||
|
||||
ctx, cancel = WithTimeout(parentCtx, 1*time.Second)
|
||||
defer cancel()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
assert.Fail(t, "context should not be done")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
ctx.Logger().Info("yay")
|
||||
}
|
||||
assert.Equal(t, 2, *infoCount)
|
||||
}
|
||||
|
||||
func TestWithLogger(t *testing.T) {
|
||||
var infoCount int
|
||||
logger := testLogger(t, func(e zapcore.Entry) {
|
||||
if e.Level == zap.InfoLevel {
|
||||
infoCount++
|
||||
}
|
||||
})
|
||||
|
||||
ctx := WithLogger(context.Background(), logger)
|
||||
assert.Equal(t, logger, ctx.Logger())
|
||||
|
||||
assert.Equal(t, 0, infoCount)
|
||||
ctx.Logger().Info("yay")
|
||||
assert.Equal(t, 1, infoCount)
|
||||
}
|
||||
|
||||
func TestAsContext(t *testing.T) {
|
||||
var gotValue any
|
||||
normalFuncThatTakesContext := func(ctx context.Context) {
|
||||
if logCtx, ok := ctx.(Context); ok {
|
||||
logCtx.Logger().Info("yay")
|
||||
}
|
||||
gotValue = ctx.Value("key")
|
||||
}
|
||||
parentCtx, infoCount := infoCounterContext(t)
|
||||
ctx := WithValue(parentCtx, "key", "value")
|
||||
|
||||
assert.Equal(t, 0, *infoCount)
|
||||
normalFuncThatTakesContext(ctx)
|
||||
assert.Equal(t, 1, *infoCount)
|
||||
assert.Equal(t, "value", gotValue)
|
||||
}
|
||||
|
||||
func TestWithValues(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger, sync := log.New("test",
|
||||
log.WithConsoleSink(&buffer),
|
||||
)
|
||||
defer func(prevLogger logr.Logger) {
|
||||
defaultLogger = prevLogger
|
||||
}(defaultLogger)
|
||||
SetDefaultLogger(logger)
|
||||
|
||||
{
|
||||
ctx1 := Background()
|
||||
ctx1.Logger().Info("only a", "a", 0)
|
||||
|
||||
ctx2 := WithValue(ctx1, "b", 1)
|
||||
ctx2.Logger().Info("only b")
|
||||
assert.Equal(t, 1, ctx2.Value("b"))
|
||||
|
||||
ctx3 := WithLogger(ctx2, ctx2.Logger().WithValues("c", 2, "d", 3))
|
||||
ctx3.Logger().Info("bcd")
|
||||
|
||||
ctx2.Logger().Info("only b again")
|
||||
|
||||
type customKey string
|
||||
ctx4 := WithValue(Background(), customKey("foo"), "bar")
|
||||
// foo:bar shouldn't be added to the logger because the key isn't a string
|
||||
ctx4.Logger().Info("foo")
|
||||
|
||||
ctx5 := WithValues(ctx2, "e", 4, "f", 5, 6, "six")
|
||||
ctx5.Logger().Info("bef")
|
||||
assert.Equal(t, "six", ctx5.Value(6))
|
||||
|
||||
ctx6 := WithValues(ctx2, "what does this do?")
|
||||
ctx6.Logger().Info("silently fail I suppose")
|
||||
}
|
||||
assert.Nil(t, sync())
|
||||
logs := strings.Split(strings.TrimSpace(buffer.String()), "\n")
|
||||
|
||||
assert.Equal(t, 7, len(logs))
|
||||
assert.Contains(t, logs[0], `{"a": 0}`)
|
||||
assert.Contains(t, logs[1], `{"b": 1}`)
|
||||
assert.Contains(t, logs[2], `{"b": 1, "c": 2, "d": 3}`)
|
||||
assert.Contains(t, logs[3], `{"b": 1}`)
|
||||
assert.NotContains(t, logs[4], `{"foo": "bar"}`)
|
||||
assert.Contains(t, logs[5], `{"b": 1, "e": 4, "f": 5}`)
|
||||
assert.Contains(t, logs[6], `silently fail`)
|
||||
assert.NotContains(t, logs[6], `what does this do?`)
|
||||
}
|
||||
|
||||
func TestDiscardLogger(t *testing.T) {
|
||||
var panicked bool
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panicked = true
|
||||
}
|
||||
assert.False(t, panicked)
|
||||
}()
|
||||
ctx := Background()
|
||||
ctx.Logger().Info("this shouldn't panic")
|
||||
}
|
||||
|
||||
func TestErrCallstack(t *testing.T) {
|
||||
c, cancel := WithCancel(Background())
|
||||
ctx := c.(logCtx)
|
||||
cancel()
|
||||
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 TestErrCallstackTimeout(t *testing.T) {
|
||||
ctx, cancel := WithTimeout(Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
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())
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package custom_detectors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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/custom_detectorspb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// The maximum number of matches from one chunk. This const is used when
|
||||
// permutating each regex match to protect the scanner from doing too much work
|
||||
// for poorly defined regexps.
|
||||
const maxTotalMatches = 100
|
||||
|
||||
// customRegexWebhook is a CustomRegex with webhook validation that is
|
||||
// guaranteed to be valid (assuming the data is not changed after
|
||||
// initialization).
|
||||
type customRegexWebhook struct {
|
||||
*custom_detectorspb.CustomRegex
|
||||
}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ detectors.Detector = (*customRegexWebhook)(nil)
|
||||
|
||||
// NewWebhookCustomRegex initializes and validates a customRegexWebhook. An
|
||||
// unexported type is intentionally returned here to ensure the values have
|
||||
// been validated.
|
||||
func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*customRegexWebhook, error) {
|
||||
// TODO: Return all validation errors.
|
||||
if err := ValidateKeywords(pb.Keywords); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidateRegex(pb.Regex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, verify := range pb.Verify {
|
||||
if err := ValidateVerifyEndpoint(verify.Endpoint, verify.Unsafe); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidateVerifyHeaders(verify.Headers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Copy only necessary data out of pb.
|
||||
return &customRegexWebhook{pb}, nil
|
||||
}
|
||||
|
||||
var httpClient = common.SaneHttpClient()
|
||||
|
||||
func (c *customRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
||||
dataStr := string(data)
|
||||
regexMatches := make(map[string][][]string, len(c.GetRegex()))
|
||||
|
||||
// Find all submatches for each regex.
|
||||
for name, regex := range c.GetRegex() {
|
||||
regex, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
// This will only happen if the regex is invalid.
|
||||
return nil, err
|
||||
}
|
||||
regexMatches[name] = regex.FindAllStringSubmatch(dataStr, -1)
|
||||
}
|
||||
|
||||
// Permutate each individual match.
|
||||
// {
|
||||
// "foo": [["match1"]]
|
||||
// "bar": [["match2"], ["match3"]]
|
||||
// }
|
||||
// becomes
|
||||
// [
|
||||
// {"foo": ["match1"], "bar": ["match2"]},
|
||||
// {"foo": ["match1"], "bar": ["match3"]},
|
||||
// ]
|
||||
matches := permutateMatches(regexMatches)
|
||||
|
||||
g := new(errgroup.Group)
|
||||
|
||||
// Create result object and test for verification.
|
||||
resultsCh := make(chan detectors.Result, maxTotalMatches)
|
||||
for _, match := range matches {
|
||||
match := match
|
||||
g.Go(func() error {
|
||||
return c.createResults(ctx, match, verify, resultsCh)
|
||||
})
|
||||
}
|
||||
|
||||
// Ignore any errors and collect as many of the results as we can.
|
||||
_ = g.Wait()
|
||||
close(resultsCh)
|
||||
|
||||
for result := range resultsCh {
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (c *customRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error {
|
||||
if common.IsDone(ctx) {
|
||||
// TODO: Log we're possibly leaving out results.
|
||||
return ctx.Err()
|
||||
}
|
||||
var raw string
|
||||
for _, values := range match {
|
||||
// values[0] contains the entire regex match.
|
||||
raw += values[0]
|
||||
}
|
||||
result := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_CustomRegex,
|
||||
Raw: []byte(raw),
|
||||
}
|
||||
|
||||
if !verify {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case results <- result:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Verify via webhook.
|
||||
jsonBody, err := json.Marshal(map[string]map[string][]string{
|
||||
c.GetName(): match,
|
||||
})
|
||||
if err != nil {
|
||||
// This should never happen, but if it does, return nil to not
|
||||
// disrupt other verification.
|
||||
return nil
|
||||
}
|
||||
// Try each config until we successfully verify.
|
||||
for _, verifyConfig := range c.GetVerify() {
|
||||
if common.IsDone(ctx) {
|
||||
// TODO: Log we're possibly leaving out results.
|
||||
return ctx.Err()
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", verifyConfig.GetEndpoint(), bytes.NewReader(jsonBody))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, header := range verifyConfig.GetHeaders() {
|
||||
key, value, found := strings.Cut(header, ":")
|
||||
if !found {
|
||||
// Should be unreachable due to validation.
|
||||
continue
|
||||
}
|
||||
req.Header.Add(key, strings.TrimLeft(value, "\t\n\v\f\r "))
|
||||
}
|
||||
res, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// TODO: Read response body.
|
||||
res.Body.Close()
|
||||
if res.StatusCode == http.StatusOK {
|
||||
result.Verified = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case results <- result:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *customRegexWebhook) Keywords() []string {
|
||||
return c.GetKeywords()
|
||||
}
|
||||
|
||||
// productIndices produces a permutation of indices for each length. Example:
|
||||
// productIndices(3, 2) -> [[0 0] [1 0] [2 0] [0 1] [1 1] [2 1]]. It returns
|
||||
// a slice of length no larger than maxTotalMatches.
|
||||
func productIndices(lengths ...int) [][]int {
|
||||
count := 1
|
||||
for _, l := range lengths {
|
||||
count *= l
|
||||
}
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
if count > maxTotalMatches {
|
||||
count = maxTotalMatches
|
||||
}
|
||||
|
||||
results := make([][]int, count)
|
||||
for i := 0; i < count; i++ {
|
||||
j := 1
|
||||
result := make([]int, 0, len(lengths))
|
||||
for _, l := range lengths {
|
||||
result = append(result, (i/j)%l)
|
||||
j *= l
|
||||
}
|
||||
results[i] = result
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// permutateMatches converts the list of all regex matches into all possible
|
||||
// permutations selecting one from each named entry in the map. For example:
|
||||
// {"foo": [matchA, matchB], "bar": [matchC]} becomes
|
||||
//
|
||||
// [{"foo": matchA, "bar": matchC}, {"foo": matchB, "bar": matchC}]
|
||||
func permutateMatches(regexMatches map[string][][]string) []map[string][]string {
|
||||
// Get a consistent order for names and their matching lengths.
|
||||
// The lengths are used in calculating the permutation so order matters.
|
||||
names := make([]string, 0, len(regexMatches))
|
||||
lengths := make([]int, 0, len(regexMatches))
|
||||
for key, value := range regexMatches {
|
||||
names = append(names, key)
|
||||
lengths = append(lengths, len(value))
|
||||
}
|
||||
|
||||
// Permutate all the indices for each match. For example, if "foo" has
|
||||
// [matchA, matchB] and "bar" has [matchC], we will get indices [0 0] [1 0].
|
||||
permutationIndices := productIndices(lengths...)
|
||||
|
||||
// Build {"foo": matchA, "bar": matchC} and {"foo": matchB, "bar": matchC}
|
||||
// from the indices.
|
||||
var matches []map[string][]string
|
||||
for _, permutation := range permutationIndices {
|
||||
candidate := make(map[string][]string, len(permutationIndices))
|
||||
for i, name := range names {
|
||||
candidate[name] = regexMatches[name][permutation[i]]
|
||||
}
|
||||
matches = append(matches, candidate)
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
func (c *customRegexWebhook) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_CustomRegex
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package custom_detectors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml"
|
||||
)
|
||||
|
||||
func TestCustomRegexTemplateParsing(t *testing.T) {
|
||||
testCustomRegexTemplateYaml := `name: Internal bi tool
|
||||
keywords:
|
||||
- secret_v1_
|
||||
- pat_v2_
|
||||
regex:
|
||||
id_pat_example: ([a-zA-Z0-9]{32})
|
||||
secret_pat_example: ([a-zA-Z0-9]{32})
|
||||
verify:
|
||||
- endpoint: http://localhost:8000/{id_pat_example}
|
||||
unsafe: true
|
||||
headers:
|
||||
- 'Authorization: Bearer {secret_pat_example.0}'
|
||||
successRanges:
|
||||
- 200-250
|
||||
- '288'`
|
||||
|
||||
var got custom_detectorspb.CustomRegex
|
||||
assert.NoError(t, protoyaml.UnmarshalStrict([]byte(testCustomRegexTemplateYaml), &got))
|
||||
assert.Equal(t, "Internal bi tool", got.Name)
|
||||
assert.Equal(t, []string{"secret_v1_", "pat_v2_"}, got.Keywords)
|
||||
assert.Equal(t, map[string]string{
|
||||
"id_pat_example": "([a-zA-Z0-9]{32})",
|
||||
"secret_pat_example": "([a-zA-Z0-9]{32})",
|
||||
}, got.Regex)
|
||||
assert.Equal(t, 1, len(got.Verify))
|
||||
assert.Equal(t, "http://localhost:8000/{id_pat_example}", got.Verify[0].Endpoint)
|
||||
assert.Equal(t, true, got.Verify[0].Unsafe)
|
||||
assert.Equal(t, []string{"Authorization: Bearer {secret_pat_example.0}"}, got.Verify[0].Headers)
|
||||
assert.Equal(t, []string{"200-250", "288"}, got.Verify[0].SuccessRanges)
|
||||
}
|
||||
|
||||
func TestCustomRegexWebhookParsing(t *testing.T) {
|
||||
testCustomRegexWebhookYaml := `name: Internal bi tool
|
||||
keywords:
|
||||
- secret_v1_
|
||||
- pat_v2_
|
||||
regex:
|
||||
id_pat_example: ([a-zA-Z0-9]{32})
|
||||
secret_pat_example: ([a-zA-Z0-9]{32})
|
||||
verify:
|
||||
- endpoint: http://localhost:8000/
|
||||
unsafe: true
|
||||
headers:
|
||||
- 'Authorization: Bearer token'`
|
||||
|
||||
var got custom_detectorspb.CustomRegex
|
||||
assert.NoError(t, protoyaml.UnmarshalStrict([]byte(testCustomRegexWebhookYaml), &got))
|
||||
assert.Equal(t, "Internal bi tool", got.Name)
|
||||
assert.Equal(t, []string{"secret_v1_", "pat_v2_"}, got.Keywords)
|
||||
assert.Equal(t, map[string]string{
|
||||
"id_pat_example": "([a-zA-Z0-9]{32})",
|
||||
"secret_pat_example": "([a-zA-Z0-9]{32})",
|
||||
}, got.Regex)
|
||||
assert.Equal(t, 1, len(got.Verify))
|
||||
assert.Equal(t, "http://localhost:8000/", got.Verify[0].Endpoint)
|
||||
assert.Equal(t, true, got.Verify[0].Unsafe)
|
||||
assert.Equal(t, []string{"Authorization: Bearer token"}, got.Verify[0].Headers)
|
||||
}
|
||||
|
||||
// TestCustomDetectorsParsing tests the full `detectors` configuration.
|
||||
func TestCustomDetectorsParsing(t *testing.T) {
|
||||
// TODO: Support both template and webhook.
|
||||
testYamlConfig := `detectors:
|
||||
- name: Internal bi tool
|
||||
keywords:
|
||||
- secret_v1_
|
||||
- pat_v2_
|
||||
regex:
|
||||
id_pat_example: ([a-zA-Z0-9]{32})
|
||||
secret_pat_example: ([a-zA-Z0-9]{32})
|
||||
verify:
|
||||
- endpoint: http://localhost:8000/
|
||||
unsafe: true
|
||||
headers:
|
||||
- 'Authorization: Bearer token'`
|
||||
|
||||
var messages custom_detectorspb.CustomDetectors
|
||||
assert.NoError(t, protoyaml.UnmarshalStrict([]byte(testYamlConfig), &messages))
|
||||
assert.Equal(t, 1, len(messages.Detectors))
|
||||
|
||||
got := messages.Detectors[0]
|
||||
assert.Equal(t, "Internal bi tool", got.Name)
|
||||
assert.Equal(t, []string{"secret_v1_", "pat_v2_"}, got.Keywords)
|
||||
assert.Equal(t, map[string]string{
|
||||
"id_pat_example": "([a-zA-Z0-9]{32})",
|
||||
"secret_pat_example": "([a-zA-Z0-9]{32})",
|
||||
}, got.Regex)
|
||||
assert.Equal(t, 1, len(got.Verify))
|
||||
assert.Equal(t, "http://localhost:8000/", got.Verify[0].Endpoint)
|
||||
assert.Equal(t, true, got.Verify[0].Unsafe)
|
||||
assert.Equal(t, []string{"Authorization: Bearer token"}, got.Verify[0].Headers)
|
||||
}
|
||||
|
||||
func TestFromData_InvalidRegEx(t *testing.T) {
|
||||
c := &customRegexWebhook{
|
||||
&custom_detectorspb.CustomRegex{
|
||||
Name: "Internal bi tool",
|
||||
Keywords: []string{"secret_v1_", "pat_v2_"},
|
||||
Regex: map[string]string{
|
||||
"test": "!!?(?:?)[a-zA-Z0-9]{32}", // invalid regex
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := c.FromData(context.Background(), false, []byte("test"))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestProductIndices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []int
|
||||
want [][]int
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
input: []int{3, 0},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "one input",
|
||||
input: []int{3},
|
||||
want: [][]int{{0}, {1}, {2}},
|
||||
},
|
||||
{
|
||||
name: "two inputs",
|
||||
input: []int{3, 2},
|
||||
want: [][]int{
|
||||
{0, 0}, {1, 0}, {2, 0},
|
||||
{0, 1}, {1, 1}, {2, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three inputs",
|
||||
input: []int{3, 2, 3},
|
||||
want: [][]int{
|
||||
{0, 0, 0}, {1, 0, 0}, {2, 0, 0},
|
||||
{0, 1, 0}, {1, 1, 0}, {2, 1, 0},
|
||||
{0, 0, 1}, {1, 0, 1}, {2, 0, 1},
|
||||
{0, 1, 1}, {1, 1, 1}, {2, 1, 1},
|
||||
{0, 0, 2}, {1, 0, 2}, {2, 0, 2},
|
||||
{0, 1, 2}, {1, 1, 2}, {2, 1, 2},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := productIndices(tt.input...)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductIndicesMax(t *testing.T) {
|
||||
got := productIndices(2, 3, 4, 5, 6)
|
||||
assert.GreaterOrEqual(t, 2*3*4*5*6, maxTotalMatches)
|
||||
assert.Equal(t, maxTotalMatches, len(got))
|
||||
}
|
||||
|
||||
func TestPermutateMatches(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string][][]string
|
||||
want []map[string][]string
|
||||
}{
|
||||
{
|
||||
name: "two matches",
|
||||
input: map[string][][]string{"foo": {{"matchA"}, {"matchB"}}, "bar": {{"matchC"}}},
|
||||
want: []map[string][]string{
|
||||
{"foo": {"matchA"}, "bar": {"matchC"}},
|
||||
{"foo": {"matchB"}, "bar": {"matchC"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := permutateMatches(tt.input)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetector(t *testing.T) {
|
||||
detector, err := NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
|
||||
Name: "test",
|
||||
// "password" is normally flagged as a false positive, but CustomRegex
|
||||
// should allow the user to decide and report it as a result.
|
||||
Keywords: []string{"password"},
|
||||
Regex: map[string]string{"regex": "password=.*"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
results, err := detector.FromData(context.Background(), false, []byte(`password="123456"`))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(results))
|
||||
assert.Equal(t, results[0].Raw, []byte(`password="123456"`))
|
||||
}
|
||||
|
||||
func BenchmarkProductIndices(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = productIndices(3, 2, 6)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package custom_detectors
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// nameGroupRegex matches `{ name . group }` ignoring any whitespace.
|
||||
var nameGroupRegex = regexp.MustCompile(`{\s*([a-zA-Z0-9-_]+)\s*(\.\s*[0-9]*)?\s*}`)
|
||||
|
||||
// RegexVarString is a string with embedded {name.group} variables. A name may
|
||||
// only contain alphanumeric, hyphen, and underscore characters. Group is
|
||||
// optional but if provided it must be a non-negative integer. If the group is
|
||||
// omitted it defaults to 0.
|
||||
type RegexVarString struct {
|
||||
original string
|
||||
// map from name to group
|
||||
variables map[string]int
|
||||
}
|
||||
|
||||
func NewRegexVarString(original string) RegexVarString {
|
||||
variables := make(map[string]int)
|
||||
|
||||
matches := nameGroupRegex.FindAllStringSubmatch(original, -1)
|
||||
for _, match := range matches {
|
||||
name, group := match[1], 0
|
||||
// The second match will start with a period followed by any number
|
||||
// of whitespace.
|
||||
if len(match[2]) > 1 {
|
||||
g, err := strconv.Atoi(strings.TrimSpace(match[2][1:]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
group = g
|
||||
}
|
||||
variables[name] = group
|
||||
}
|
||||
|
||||
return RegexVarString{
|
||||
original: original,
|
||||
variables: variables,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package custom_detectors
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVarString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantVars map[string]int
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: "{}",
|
||||
wantVars: map[string]int{},
|
||||
},
|
||||
{
|
||||
name: "no subgroup",
|
||||
input: "{hello}",
|
||||
wantVars: map[string]int{
|
||||
"hello": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with subgroup",
|
||||
input: "{hello.123}",
|
||||
wantVars: map[string]int{
|
||||
"hello": 123,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subgroup with spaces",
|
||||
input: "{\thell0 . 123 }",
|
||||
wantVars: map[string]int{
|
||||
"hell0": 123,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple groups",
|
||||
input: "foo {bar} {bazz.buzz} {buzz.2}",
|
||||
wantVars: map[string]int{
|
||||
"bar": 0,
|
||||
"buzz": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested groups",
|
||||
input: "{foo {bar}}",
|
||||
wantVars: map[string]int{
|
||||
"bar": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "decimal without number",
|
||||
input: "{foo.}",
|
||||
wantVars: map[string]int{
|
||||
"foo": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative number",
|
||||
input: "{foo.-1}",
|
||||
wantVars: map[string]int{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := NewRegexVarString(tt.input)
|
||||
assert.Equal(t, tt.input, got.original)
|
||||
assert.Equal(t, tt.wantVars, got.variables)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package custom_detectors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ValidateKeywords(keywords []string) error {
|
||||
if len(keywords) == 0 {
|
||||
return fmt.Errorf("no keywords")
|
||||
}
|
||||
|
||||
for _, keyword := range keywords {
|
||||
if len(keyword) == 0 {
|
||||
return fmt.Errorf("empty keyword")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateRegex(regex map[string]string) error {
|
||||
if len(regex) == 0 {
|
||||
return fmt.Errorf("no regex")
|
||||
}
|
||||
for name, reg := range regex {
|
||||
if _, err := regexp.Compile(reg); err != nil {
|
||||
return fmt.Errorf("regex '%s': %w", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateVerifyEndpoint(endpoint string, unsafe bool) error {
|
||||
if len(endpoint) == 0 {
|
||||
return fmt.Errorf("no endpoint")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(endpoint, "http://") && !unsafe {
|
||||
return fmt.Errorf("http endpoint must have unsafe=true")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateVerifyHeaders(headers []string) error {
|
||||
for _, header := range headers {
|
||||
if !strings.Contains(header, ":") {
|
||||
return fmt.Errorf("header %q must contain a colon", header)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateVerifyRanges(ranges []string) error {
|
||||
const httpLowerRange = 100
|
||||
const httpUpperRange = 599
|
||||
|
||||
for _, successRange := range ranges {
|
||||
if !strings.Contains(successRange, "-") {
|
||||
httpCode, err := strconv.Atoi(successRange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to convert http code to int %q", successRange)
|
||||
}
|
||||
|
||||
if httpCode < httpLowerRange || httpCode > httpUpperRange {
|
||||
return fmt.Errorf("invalid http status code %q", successRange)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
httpRange := strings.Split(successRange, "-")
|
||||
if len(httpRange) != 2 {
|
||||
return fmt.Errorf("invalid range format %q", successRange)
|
||||
}
|
||||
|
||||
lowerBound, err := strconv.Atoi(httpRange[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to convert lower bound to int %q", successRange)
|
||||
}
|
||||
|
||||
upperBound, err := strconv.Atoi(httpRange[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to convert upper bound to int %q", successRange)
|
||||
}
|
||||
|
||||
if lowerBound > upperBound {
|
||||
return fmt.Errorf("lower bound greater than upper bound on range %q", successRange)
|
||||
}
|
||||
|
||||
if lowerBound < httpLowerRange || upperBound > httpUpperRange {
|
||||
return fmt.Errorf("invalid http status code range %q", successRange)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateRegexVars(regex map[string]string, body ...string) error {
|
||||
for _, b := range body {
|
||||
matches := NewRegexVarString(b).variables
|
||||
for match := range matches {
|
||||
if _, ok := regex[match]; !ok {
|
||||
return fmt.Errorf("body %q contains an unknown variable", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package custom_detectors
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCustomDetectorsKeywordValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test empty list of keywords",
|
||||
input: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test empty keyword",
|
||||
input: []string{""},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test valid keywords",
|
||||
input: []string{"hello", "world"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidateKeywords(tt.input)
|
||||
|
||||
if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
|
||||
t.Errorf("ValidateKeywords() error = %v, wantErr %v", got, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDetectorsRegexValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test list of keywords",
|
||||
input: map[string]string{
|
||||
"id_pat_example": "([a-zA-Z0-9]{32})",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test empty list of keywords",
|
||||
input: map[string]string{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test invalid regex",
|
||||
input: map[string]string{
|
||||
"test": "!!?(?:?)[a-zA-Z0-9]{32}",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidateRegex(tt.input)
|
||||
|
||||
if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
|
||||
t.Errorf("ValidateRegex() error = %v, wantErr %v", got, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDetectorsVerifyEndpointValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
unsafe bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test http endpoint with unsafe flag",
|
||||
endpoint: "http://localhost:8000/{id_pat_example}",
|
||||
unsafe: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test http endpoint without unsafe flag",
|
||||
endpoint: "http://localhost:8000/{id_pat_example}",
|
||||
unsafe: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test https endpoint with unsafe flag",
|
||||
endpoint: "https://localhost:8000/{id_pat_example}",
|
||||
unsafe: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test https endpoint without unsafe flag",
|
||||
endpoint: "https://localhost:8000/{id_pat_example}",
|
||||
unsafe: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidateVerifyEndpoint(tt.endpoint, tt.unsafe)
|
||||
|
||||
if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
|
||||
t.Errorf("ValidateVerifyEndpoint() error = %v, wantErr %v", got, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDetectorsVerifyHeadersValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
headers []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test single header",
|
||||
headers: []string{"Authorization: Bearer {secret_pat_example.0}"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test invalid header",
|
||||
headers: []string{"Hello world"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test ugly header",
|
||||
headers: []string{"Hello:::::::world::hi:"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test empty header",
|
||||
headers: []string{},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidateVerifyHeaders(tt.headers)
|
||||
|
||||
if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
|
||||
t.Errorf("ValidateVerifyHeaders() error = %v, wantErr %v", got, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDetectorsVerifyRangeValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ranges []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test multiple mixed ranges",
|
||||
ranges: []string{"200", "300-350"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test invalid non-number range",
|
||||
ranges: []string{"hi"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test invalid lower to upper range",
|
||||
ranges: []string{"200-100"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test invalid http range",
|
||||
ranges: []string{"400-1000"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Test multiple ranges with invalid inputs",
|
||||
ranges: []string{"322", "hello-world", "100-200"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidateVerifyRanges(tt.ranges)
|
||||
|
||||
if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
|
||||
t.Errorf("ValidateVerifyRanges() error = %v, wantErr %v", got, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomDetectorsVerifyRegexVarsValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
regex map[string]string
|
||||
body string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Regex defined but not used in body",
|
||||
regex: map[string]string{"id": "[0-9]{1,10}", "id_pat_example": "([a-zA-Z0-9]{32})"},
|
||||
body: "hello world",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Regex defined and is used in body",
|
||||
regex: map[string]string{"id": "[0-9]{1,10}", "id_pat_example": "([a-zA-Z0-9]{32})"},
|
||||
body: "hello world {id}",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Regex var in body but not defined",
|
||||
regex: map[string]string{"id": "[0-9]{1,10}", "id_pat_example": "([a-zA-Z0-9]{32})"},
|
||||
body: "hello world {hello}",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidateRegexVars(tt.regex, tt.body)
|
||||
|
||||
if (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {
|
||||
t.Errorf("ValidateRegexVars() error = %v, wantErr %v", got, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
func DefaultDecoders() []Decoder {
|
||||
return []Decoder{
|
||||
&Plain{},
|
||||
&UTF8{},
|
||||
&Base64{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package decoders
|
||||
|
||||
import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
)
|
||||
|
||||
// Ensure the Decoder satisfies the interface at compile time
|
||||
var _ Decoder = (*Plain)(nil)
|
||||
|
||||
type Plain struct{}
|
||||
|
||||
func (d *Plain) FromChunk(chunk *sources.Chunk) *sources.Chunk {
|
||||
return chunk
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package decoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
)
|
||||
|
||||
type UTF8 struct{}
|
||||
|
||||
func (d *UTF8) FromChunk(chunk *sources.Chunk) *sources.Chunk {
|
||||
if chunk == nil || len(chunk.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !utf8.Valid(chunk.Data) {
|
||||
chunk.Data = extractSubstrings(chunk.Data)
|
||||
return chunk
|
||||
}
|
||||
|
||||
return chunk
|
||||
}
|
||||
|
||||
// extractSubstrings performs similarly to the strings binutil,
|
||||
// extacting contigous portions of printable characters that we care
|
||||
// about from some bytes
|
||||
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)
|
||||
})
|
||||
|
||||
keep := [][]byte{}
|
||||
for _, field := range fields {
|
||||
// Remove fields shorter than 6 characters.
|
||||
if bts := bytes.TrimSpace(field); len(bts) > 5 {
|
||||
keep = append(keep, bts)
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.Join(keep, []byte("\n"))
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package decoders
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
)
|
||||
|
||||
func TestUTF8_FromChunk(t *testing.T) {
|
||||
type args struct {
|
||||
chunk *sources.Chunk
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
d *UTF8
|
||||
args args
|
||||
want *sources.Chunk
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "successful UTF8 decode",
|
||||
d: &UTF8{},
|
||||
args: args{
|
||||
chunk: &sources.Chunk{Data: []byte("plain 'ol chunk that should decode successfully")},
|
||||
},
|
||||
want: &sources.Chunk{Data: []byte("plain 'ol chunk that should decode successfully")},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "successful binary decode",
|
||||
d: &UTF8{},
|
||||
args: args{
|
||||
chunk: &sources.Chunk{Data: []byte("\xf0\x28\x8c\x28 not-entirely utf8 chunk that should decode successfully")},
|
||||
},
|
||||
want: &sources.Chunk{Data: []byte("( not-entirely utf8 chunk that should decode successfully")},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "unsuccessful decode",
|
||||
d: &UTF8{},
|
||||
args: args{
|
||||
chunk: nil,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &UTF8{}
|
||||
got := d.FromChunk(tt.args.chunk)
|
||||
if got != nil && tt.want != nil {
|
||||
if diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != "" {
|
||||
t.Errorf("%s: Plain.FromChunk() diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
} else {
|
||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||
t.Errorf("%s: Plain.FromChunk() diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var testBytes = []byte(`some words with random spaces and
|
||||
|
||||
newlines with
|
||||
arbitrary length
|
||||
of
|
||||
|
||||
hey
|
||||
|
||||
the lines themselves.
|
||||
|
||||
and
|
||||
short
|
||||
words
|
||||
that
|
||||
go
|
||||
away.`)
|
||||
|
||||
func Benchmark_extractSubstrings(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
extractSubstrings(testBytes)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package abbysale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -52,7 +51,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
req.Header.Add("x-api-key", fmt.Sprintf("%s", resMatch))
|
||||
req.Header.Add("x-api-key", resMatch)
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
@@ -70,5 +69,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Abbysale
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Abstract
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package abuseipdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
|
||||
"io"
|
||||
"net/http"
|
||||
// "log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
@@ -58,11 +56,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
req.Header.Add("Key", resMatch)
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err == nil {
|
||||
bodyString := string(bodyBytes)
|
||||
validResponse := strings.Contains(bodyString, `ipAddress`)
|
||||
//errCode := strings.Contains(bodyString, `AbuseIPDB APIv2 Server.`)
|
||||
// errCode := strings.Contains(bodyString, `AbuseIPDB APIv2 Server.`)
|
||||
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||
@@ -83,5 +81,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AbuseIPDB
|
||||
}
|
||||
|
||||
@@ -68,5 +68,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Accuweather
|
||||
}
|
||||
|
||||
@@ -68,5 +68,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AdafruitIO
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_AdobeIO,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -79,5 +80,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AdobeIO
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Adzuna,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -77,5 +78,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Adzuna
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Aeroworkflow,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -80,5 +81,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Aeroworkflow
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Agora,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resSecret),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -80,5 +81,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Agora
|
||||
}
|
||||
|
||||
@@ -71,5 +71,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Aha
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_AirbrakeProjectKey,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -84,5 +85,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AirbrakeProjectKey
|
||||
}
|
||||
|
||||
@@ -68,5 +68,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AirbrakeUserKey
|
||||
}
|
||||
|
||||
@@ -71,5 +71,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Airship
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
DetectorType: detectorspb.DetectorType_AirtableApiKey,
|
||||
Redacted: appRes,
|
||||
Raw: []byte(keyRes),
|
||||
RawV2: []byte(keyRes + appRes),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -78,5 +79,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AirtableApiKey
|
||||
}
|
||||
|
||||
@@ -71,5 +71,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AirVisual
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package alchemy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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{}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time.
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
client = 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 {
|
||||
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 {
|
||||
// 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 detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Alchemy
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package alchemy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"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", "detectors4")
|
||||
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
|
||||
}{
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte("You cannot find the secret within"),
|
||||
verify: true,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := Scanner{}
|
||||
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("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])
|
||||
}
|
||||
got[i].Raw = nil
|
||||
}
|
||||
if diff := pretty.Compare(got, tt.want); 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) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := s.FromData(ctx, false, data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -74,5 +74,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Alconost
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Alegra,
|
||||
Raw: []byte(tokenPatMatch),
|
||||
RawV2: []byte(tokenPatMatch + userPatMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -79,5 +80,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Alegra
|
||||
}
|
||||
|
||||
@@ -72,5 +72,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AletheiaApi
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_AlgoliaAdminKey,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -77,5 +78,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
}
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AlgoliaAdminKey
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Alibaba,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -103,7 +104,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
params.Add("Version", "2014-05-26")
|
||||
|
||||
stringToSign := buildStringToSign(req.Method, params.Encode())
|
||||
signature := GetSignature(stringToSign, resMatch+"&") //Get Signature HMAC SHA1
|
||||
signature := GetSignature(stringToSign, resMatch+"&") // Get Signature HMAC SHA1
|
||||
params.Add("Signature", signature)
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
@@ -127,5 +128,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Alibaba
|
||||
}
|
||||
|
||||
@@ -69,5 +69,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AlienVault
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package allsports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
|
||||
"io"
|
||||
"net/http"
|
||||
// "log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
@@ -58,7 +56,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -77,5 +75,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Allsports
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package amadeus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Amadeus,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resSecret),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -65,7 +66,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -85,5 +86,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Amadeus
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Ambee
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_AmplitudeApiKey,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resSecretMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -78,5 +79,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AmplitudeApiKey
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Anypoint,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + orgRes),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -79,5 +80,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Anypoint
|
||||
}
|
||||
|
||||
@@ -69,5 +69,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Apacta
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -57,11 +57,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
body, errBody := ioutil.ReadAll(res.Body)
|
||||
body, errBody := io.ReadAll(res.Body)
|
||||
|
||||
var result Response
|
||||
if errBody == nil {
|
||||
json.Unmarshal(body, &result)
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 && result.ReturnCode == 0 {
|
||||
s1.Verified = true
|
||||
@@ -78,9 +80,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ReturnCode int `json:"return_code"`
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Api2Cart
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_ApiDeck,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -81,5 +82,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_ApiDeck
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Apiflash,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resUrlMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -77,5 +78,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Apiflash
|
||||
}
|
||||
|
||||
@@ -81,5 +81,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_ApiFonica
|
||||
}
|
||||
|
||||
@@ -68,5 +68,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Apify
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package apilayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Scanner struct{}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
client = common.SaneHttpClient()
|
||||
|
||||
// Make sure that your group is surrounded in boundry characters such as below to reduce false positives
|
||||
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"apilayer"}) + `\b([a-zA-Z0-9]{32})\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{"apilayer"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify Apilayer 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_Apilayer,
|
||||
Raw: []byte(resMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.apilayer.com/number_verification/countries", nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
req.Header.Add("apikey", resMatch)
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||
s1.Verified = true
|
||||
} else {
|
||||
// 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 detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Apilayer
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package apilayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
func TestApilayer_FromChunk(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
secret := testSecrets.MustGetField("APILAYER")
|
||||
inactiveSecret := testSecrets.MustGetField("APILAYER_INACTIVE")
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
data []byte
|
||||
verify bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
s Scanner
|
||||
args args
|
||||
want []detectors.Result
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "found, verified",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a apilayer secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_Apilayer,
|
||||
Verified: true,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "found, unverified",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a apilayer 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_Apilayer,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte("You cannot find the secret within"),
|
||||
verify: true,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := Scanner{}
|
||||
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Apilayer.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])
|
||||
}
|
||||
got[i].Raw = nil
|
||||
}
|
||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||
t.Errorf("Apilayer.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) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := s.FromData(ctx, false, data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_APIMatic,
|
||||
Raw: []byte(userPatMatch),
|
||||
RawV2: []byte(userPatMatch + passPatMatch),
|
||||
}
|
||||
if verify {
|
||||
timeout := 10 * time.Second
|
||||
@@ -81,5 +82,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
}
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_APIMatic
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_ApiScience
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_APITemplate
|
||||
}
|
||||
|
||||
@@ -69,5 +69,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Apollo
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Appcues,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resUserMatch),
|
||||
}
|
||||
if verify {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.appcues.com/v2/accounts/%s/flows", resIdMatch), nil)
|
||||
@@ -89,5 +90,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Appcues
|
||||
}
|
||||
|
||||
@@ -73,5 +73,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Appfollow
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package appointedd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"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{}
|
||||
|
||||
// Ensure the Scanner satisfies the interface at compile time
|
||||
var _ detectors.Detector = (*Scanner)(nil)
|
||||
|
||||
var (
|
||||
client = common.SaneHttpClient()
|
||||
|
||||
// Make sure that your group is surrounded in boundry characters such as below to reduce false positives
|
||||
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appointedd"}) + `\b([a-zA-Z0-9=+]{88})`)
|
||||
)
|
||||
|
||||
// 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{"appointedd"}
|
||||
}
|
||||
|
||||
// FromData will find and optionally verify appointedd 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_Appointedd,
|
||||
Raw: []byte(resMatch),
|
||||
}
|
||||
if verify {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appointedd.com/v1/availability/slots", nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
req.Header.Add("X-API-KEY", resMatch)
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
body := string(bodyBytes)
|
||||
|
||||
if strings.Contains(body, "total") {
|
||||
s1.Verified = true
|
||||
} else {
|
||||
if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Appointedd
|
||||
}
|
||||
+16
-16
@@ -1,7 +1,4 @@
|
||||
//go:build detectors
|
||||
// +build detectors
|
||||
|
||||
package moonclerck
|
||||
package appointedd
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -10,21 +7,21 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||
)
|
||||
|
||||
func TestMoonclerck_FromChunk(t *testing.T) {
|
||||
func TestAppointedd_FromChunk(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
secret := testSecrets.MustGetField("MOONCLERCK")
|
||||
inactiveSecret := testSecrets.MustGetField("MOONCLERCK_INACTIVE")
|
||||
secret := testSecrets.MustGetField("APPOINTEDD")
|
||||
inactiveSecret := testSecrets.MustGetField("APPOINTEDD_INACTIVE")
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -43,12 +40,12 @@ func TestMoonclerck_FromChunk(t *testing.T) {
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a moonclerck secret %s within", secret)),
|
||||
data: []byte(fmt.Sprintf("You can find a appointedd secret %s within", secret)),
|
||||
verify: true,
|
||||
},
|
||||
want: []detectors.Result{
|
||||
{
|
||||
DetectorType: detectorspb.DetectorType_Moonclerk,
|
||||
DetectorType: detectorspb.DetectorType_Appointedd,
|
||||
Verified: true,
|
||||
},
|
||||
},
|
||||
@@ -59,12 +56,12 @@ func TestMoonclerck_FromChunk(t *testing.T) {
|
||||
s: Scanner{},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: []byte(fmt.Sprintf("You can find a moonclerck secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
|
||||
data: []byte(fmt.Sprintf("You can find a appointedd 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_Moonclerk,
|
||||
DetectorType: detectorspb.DetectorType_Appointedd,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
@@ -87,7 +84,7 @@ func TestMoonclerck_FromChunk(t *testing.T) {
|
||||
s := Scanner{}
|
||||
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Moonclerck.FromData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("Appointedd.FromData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
@@ -97,7 +94,7 @@ func TestMoonclerck_FromChunk(t *testing.T) {
|
||||
got[i].Raw = nil
|
||||
}
|
||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||
t.Errorf("Moonclerck.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
t.Errorf("Appointedd.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -109,7 +106,10 @@ func BenchmarkFromData(benchmark *testing.B) {
|
||||
for name, data := range detectors.MustGetBenchmarkData() {
|
||||
benchmark.Run(name, func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
s.FromData(ctx, false, data)
|
||||
_, err := s.FromData(ctx, false, data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AppSynergy
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package apptivo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Apptivo,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -62,7 +63,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -88,5 +89,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Apptivo
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_ArtifactoryAccessToken,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resURLMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -80,5 +81,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_ArtifactoryAccessToken
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Artsy,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resIdMatch),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -79,5 +80,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Artsy
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AsanaOauth
|
||||
}
|
||||
|
||||
@@ -69,5 +69,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AsanaPersonalAccessToken
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_AssemblyAI
|
||||
}
|
||||
|
||||
@@ -70,5 +70,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Atera
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package audd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -55,7 +55,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err == nil {
|
||||
bodyString := string(bodyBytes)
|
||||
validResponse := strings.Contains(bodyString, `"status":"success"`)
|
||||
@@ -79,5 +79,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
results = append(results, s1)
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Audd
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
DetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,
|
||||
Redacted: domainRes,
|
||||
Raw: []byte(managementApiTokenRes),
|
||||
RawV2: []byte(managementApiTokenRes + domainRes),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -87,5 +88,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Auth0ManagementApiToken
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ package auth0oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
// "fmt"
|
||||
// "log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
@@ -65,6 +62,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
DetectorType: detectorspb.DetectorType_Auth0oauth,
|
||||
Redacted: clientIdRes,
|
||||
Raw: []byte(clientSecretRes),
|
||||
RawV2: []byte(clientIdRes + clientSecretRes),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -90,7 +88,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer res.Body.Close()
|
||||
bodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
bodyBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -116,5 +114,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Auth0oauth
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1 := detectors.Result{
|
||||
DetectorType: detectorspb.DetectorType_Autodesk,
|
||||
Raw: []byte(resMatch),
|
||||
RawV2: []byte(resMatch + resSecret),
|
||||
}
|
||||
|
||||
if verify {
|
||||
@@ -79,5 +80,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
}
|
||||
|
||||
return detectors.CleanResults(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) Type() detectorspb.DetectorType {
|
||||
return detectorspb.DetectorType_Autodesk
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user