Compare commits

..

2 Commits

Author SHA1 Message Date
bill-rich 503f76351e wip 2022-08-01 15:54:15 -07:00
bill-rich ad47d3cfee wip 2022-08-01 15:53:32 -07:00
894 changed files with 4351 additions and 19658 deletions
+3 -8
View File
@@ -1,12 +1,7 @@
name: Scan for secrets
on:
push:
tags:
- v*
branches:
- main
on: push
jobs:
test:
@@ -22,9 +17,9 @@ jobs:
fetch-depth: 0
ref: ${{ github.head_ref }}
- name: Dogfood
uses: ./
uses: onethree111/trufflehog@latest
id: dogfood
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
head: HEAD
+3 -3
View File
@@ -23,12 +23,12 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- id: 'auth'
uses: 'google-github-actions/auth@v1'
uses: 'google-github-actions/auth@v0'
with:
workload_identity_provider: 'projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
service_account: 'github-ci-external@trufflehog-testing.iam.gserviceaccount.com'
- name: Test
run: make test-integration
run: make test
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@v1'
uses: 'google-github-actions/auth@v0'
with:
workload_identity_provider: 'projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
service_account: 'github-ci-external@trufflehog-testing.iam.gserviceaccount.com'
-8
View File
@@ -13,8 +13,6 @@ builds:
dockers:
- image_templates: ["trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64"]
dockerfile: Dockerfile.goreleaser
extra_files:
- entrypoint.sh
use: buildx
build_flag_templates:
- --platform=linux/amd64
@@ -28,8 +26,6 @@ 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
@@ -42,8 +38,6 @@ 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
@@ -57,8 +51,6 @@ 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
+2 -3
View File
@@ -9,9 +9,8 @@ RUN --mount=type=cache,target=/go/pkg/mod \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
FROM alpine:3.15
RUN apk add --no-cache git ca-certificates \
&& rm -rf /var/cache/apk/* && \
update-ca-certificates
RUN apk add --no-cache git
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /build/trufflehog /usr/bin/trufflehog
COPY entrypoint.sh /etc/entrypoint.sh
RUN chmod +x /etc/entrypoint.sh
+3 -6
View File
@@ -1,10 +1,7 @@
FROM alpine:3.15
RUN apk add --no-cache git ca-certificates \
&& rm -rf /var/cache/apk/* && \
update-ca-certificates
RUN apk add --no-cache git
WORKDIR /usr/bin/
COPY trufflehog .
COPY entrypoint.sh /etc/entrypoint.sh
RUN chmod +x /etc/entrypoint.sh
ENTRYPOINT ["/etc/entrypoint.sh"]
ENTRYPOINT ["/usr/bin/trufflehog"]
-6
View File
@@ -30,9 +30,6 @@ test-failing:
test:
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/ | grep -v pkg/detectors)
@@ -60,6 +57,3 @@ release-protos-image:
snifftest:
./hack/snifftest/snifftest.sh
test-release:
goreleaser release --rm-dist --skip-publish --snapshot
+9 -196
View File
@@ -6,14 +6,14 @@
---
<div align="center">
<center>
[![CI Status](https://github.com/trufflesecurity/trufflehog/actions/workflows/release.yml/badge.svg)](https://github.com/trufflesecurity/trufflehog/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/trufflesecurity/trufflehog/v3)](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)
[![License](https://img.shields.io/badge/license-AGPL--3.0-brightgreen)](/LICENSE)
[![Total Detectors](https://img.shields.io/github/directory-file-count/trufflesecurity/truffleHog/pkg/detectors?label=Total%20Detectors&type=dir)](/pkg/detectors)
[![Total Detectors](https://shields-staging.herokuapp.com/github/directory-file-count/trufflesecurity/truffleHog/pkg/detectors?label=Total%20Detectors&type=dir)](/pkg/detectors) <!-- Badge must be run from staging, see badges/shields#5967 -->
</div>
</center>
---
@@ -31,66 +31,6 @@ https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkii
docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=trufflesecurity
```
## Examples
### Example 1: Scan a repo for only verified secrets
Command:
```
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
```
trufflehog github --org=trufflesecurity --only-verified
```
### Example 3: Scan a GitHub Repo for only verified keys and get JSON output
Command:
```
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
```
trufflehog s3 --bucket=<bucket name> --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.
@@ -130,7 +70,7 @@ docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --repo ht
#### **Apple M1 users**
The `linux/arm64` image is better to run on the M1 than the amd64 image.
Even better is running the native darwin binary available, but there is no container image for that.
Even better is running the native darwin binary avilable, but there is not 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
@@ -147,7 +87,8 @@ Help with setting up this packaging would be appreciated!
### 5. Brew
```bash
brew install trufflesecurity/trufflehog/trufflehog
brew tap trufflesecurity/trufflehog
brew install trufflehog
```
## Usage
@@ -162,7 +103,7 @@ TruffleHog has a sub-command for each source of data that you may want to scan:
- syslog
- file and stdin (coming soon)
Each subcommand can have options that you can see with the `--help` flag provided to the sub command:
Each subcommand can have options that you can see with the `-h` flag provided to the sub command:
```
$ trufflehog git --help
@@ -179,7 +120,6 @@ Flags:
--concurrency=1 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.
--print-avg-detector-time Print the average time spent on each detector.
--no-update Don't check for updates.
-i, --include-paths=INCLUDE-PATHS
@@ -195,7 +135,7 @@ Flags:
--regex No-op flag for backwards compat.
Args:
<uri> Git repository URL. https://, file://, or ssh:// schema expected.
<uri> Git repository URL. https:// or file:// schema expected.
```
For example, to scan a `git` repository, start with
@@ -229,8 +169,6 @@ docker run -it -v "$PWD:/pwd" trufflesecurity/trufflehog:latest github --org=tru
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
@@ -249,131 +187,13 @@ jobs:
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
uses: trufflesecurity/trufflehog@v3.4.3
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 -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 --directory /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)].
@@ -397,10 +217,3 @@ 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.
+1 -3
View File
@@ -8,8 +8,7 @@ inputs:
required: true
base:
description: Start scanning from here (usually main branch).
required: false
default: ''
required: true
head:
description: Scan commits until here (usually dev branch).
required: false
@@ -31,5 +30,4 @@ runs:
- --branch
- ${{ inputs.head }}
- --fail
- --no-update
- ${{ inputs.extra_args }}
+41 -83
View File
@@ -4,71 +4,57 @@ 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-custom10
replace github.com/zricethezav/gitleaks/v8 => github.com/trufflesecurity/gitleaks/v8 v8.6.1-custom9
replace github.com/gitleaks/go-gitdiff => github.com/trufflesecurity/go-gitdiff v0.7.6-zombies2
require (
cloud.google.com/go/secretmanager v1.9.0
cloud.google.com/go/secretmanager v1.5.0
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/TheZeroSlave/zapsentry v1.12.0
github.com/aws/aws-sdk-go v1.44.83
github.com/bill-rich/disk-buffer-reader v0.1.7
github.com/aws/aws-sdk-go v1.44.61
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/crewjam/rfc5424 v0.1.0
github.com/denisenkom/go-mssqldb v0.12.3
github.com/envoyproxy/protoc-gen-validate v0.9.1
github.com/envoyproxy/protoc-gen-validate v0.6.7
github.com/fatih/color v1.13.0
github.com/felixge/fgprof v0.9.3
github.com/getsentry/sentry-go v0.16.0
github.com/felixge/fgprof v0.9.2
github.com/gitleaks/go-gitdiff v0.7.4
github.com/go-errors/errors v1.4.2
github.com/go-git/go-git/v5 v5.5.1
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/go-git/go-git/v5 v5.4.2
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/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/jlaffaye/ftp v0.1.0
github.com/joho/godotenv v1.4.0
github.com/jpillora/overseer v1.1.6
github.com/kylelemons/godebug v1.1.0
github.com/lib/pq v1.10.7
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-sqlite3 v1.14.16
github.com/mholt/archiver/v4 v4.0.0-alpha.7
github.com/mattn/go-colorable v0.1.12
github.com/paulbellamy/ratecounter v0.2.0
github.com/pkg/errors v0.9.1
github.com/rabbitmq/amqp091-go v1.5.0
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.9.0
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.0
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
github.com/xanzy/go-gitlab v0.77.0
go.mongodb.org/mongo-driver v1.11.1
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.4.0
golang.org/x/exp v0.0.0-20221018205818-5c77f4b2bbd7
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783
golang.org/x/sync v0.1.0
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c
google.golang.org/protobuf v1.28.1
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
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/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect
cloud.google.com/go/iam v0.6.0 // indirect
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
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
@@ -76,77 +62,49 @@ 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/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/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // 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/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/emirpasic/gods v1.12.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang-jwt/jwt/v4 v4.4.1 // 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/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-github/v45 v45.2.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.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // 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/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/imdario/mergo v0.3.12 // 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 v1.2.0 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/mattn/go-isatty v0.0.14 // 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.2.3 // indirect
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 // indirect
github.com/pmezard/go-difflib v1.0.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/xanzy/ssh-agent v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.3.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.3.0 // indirect
google.golang.org/api v0.102.0 // indirect
golang.org/x/mod v0.5.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // 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
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/grpc v1.47.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
)
+586 -246
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -123,7 +123,3 @@ 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
```
+1 -10
View File
@@ -49,12 +49,8 @@ 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
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.
3. 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:
@@ -104,8 +100,3 @@ 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 -1
View File
@@ -2,6 +2,7 @@ package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -76,7 +77,7 @@ func mustWriteTemplates(jobs []templateJob) {
// Write the files from templates.
for _, job := range jobs {
tmplBytes, err := os.ReadFile(job.TemplatePath)
tmplBytes, err := ioutil.ReadFile(job.TemplatePath)
if err != nil {
log.Fatal(err)
}
+2 -2
View File
@@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"os"
"reflect"
@@ -15,7 +16,6 @@ import (
"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"
@@ -162,7 +162,7 @@ func main() {
defer sem.Release(1)
defer wgChunkers.Done()
log.Infof("cloning %s", r)
path, repo, err := git.CloneRepoUsingUnauthenticated(ctx, r)
path, repo, err := git.CloneRepoUsingUnauthenticated(r)
if err != nil {
log.Fatal(err)
}
+38 -111
View File
@@ -1,7 +1,9 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"os"
@@ -15,40 +17,34 @@ import (
"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/engine"
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
"github.com/trufflesecurity/trufflehog/v3/pkg/output"
"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()
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()
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()
// 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()
gitScan = cli.Command("git", "Find credentials in git repositories.")
gitScanURI = gitScan.Arg("uri", "Git repository URL. https://, file://, or ssh:// schema expected.").Required().String()
gitScanURI = gitScan.Arg("uri", "Git repository URL. https:// or file:// 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()
@@ -62,19 +58,15 @@ var (
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()
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()
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()
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. 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()
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()
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()
@@ -84,8 +76,8 @@ var (
// 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. 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()
s3ScanKey = s3Scan.Flag("key", "S3 key used to authenticate.").String()
s3ScanSecret = s3Scan.Flag("secret", "S3 secret used to authenticate.").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()
@@ -114,11 +106,9 @@ func init() {
}
switch {
case *trace:
log.SetLevel(5)
logrus.SetLevel(logrus.TraceLevel)
logrus.Debugf("running version %s", version.BuildVersion)
case *debug:
log.SetLevel(2)
logrus.SetLevel(logrus.DebugLevel)
logrus.Debugf("running version %s", version.BuildVersion)
default:
@@ -150,7 +140,7 @@ func main() {
func run(state overseer.State) {
if *debug {
logrus.Debugf("trufflehog %s", version.BuildVersion)
fmt.Println("trufflehog " + version.BuildVersion)
}
if *githubScanToken != "" {
@@ -175,27 +165,12 @@ func run(state overseer.State) {
}
}()
}
logger, sync := log.New("trufflehog", log.WithConsoleSink(os.Stderr))
context.SetDefaultLogger(logger)
defer func() { _ = sync() }()
conf := &config.Config{}
if *configFilename != "" {
var err error
conf, err = config.Read(*configFilename)
if err != nil {
logger.Error(err, "error parsing the provided configuration file")
os.Exit(1)
}
}
ctx := context.TODO()
e := engine.Start(ctx,
engine.WithConcurrency(*concurrency),
engine.WithDecoders(decoders.DefaultDecoders()...),
engine.WithDetectors(!*noVerification, engine.DefaultDetectors()...),
engine.WithDetectors(!*noVerification, conf.Detectors...),
engine.WithFilterUnverified(*filterUnverified),
)
filter, err := common.FilterFromFiles(*gitScanIncludePaths, *gitScanExcludePaths)
@@ -207,95 +182,48 @@ func run(state overseer.State) {
var remote bool
switch cmd {
case gitScan.FullCommand():
repoPath, remote, err = git.PrepareRepoSinceCommit(ctx, *gitScanURI, *gitScanSinceCommit)
repoPath, remote, err = git.PrepareRepoSinceCommit(*gitScanURI, *gitScanSinceCommit)
if err != nil || repoPath == "" {
logrus.WithError(err).Fatal("error preparing git repo for scanning")
}
if remote {
defer os.RemoveAll(repoPath)
}
g := func(c *sources.Config) {
c.RepoPath = repoPath
c.HeadRef = *gitScanBranch
c.BaseRef = *gitScanSinceCommit
c.MaxDepth = *gitScanMaxDepth
c.Filter = filter
}
if err = e.ScanGit(ctx, sources.NewConfig(g)); err != nil {
logrus.WithError(err).Fatal("Failed to scan Git.")
err = e.ScanGit(ctx, repoPath, *gitScanBranch, *gitScanSinceCommit, *gitScanMaxDepth, filter)
if err != nil {
logrus.WithError(err).Fatal("Failed to scan git.")
}
case githubScan.FullCommand():
if len(*githubScanOrgs) == 0 && len(*githubScanRepos) == 0 {
logrus.Fatal("You must specify at least one organization or repository.")
log.Fatal("You must specify at least one organization or repository.")
}
github := func(c *sources.Config) {
c.Endpoint = *githubScanEndpoint
c.Repos = *githubScanRepos
c.Orgs = *githubScanOrgs
c.Token = *githubScanToken
c.IncludeForks = *githubIncludeForks
c.IncludeMembers = *githubIncludeMembers
c.Concurrency = *concurrency
c.ExcludeRepos = *githubExcludeRepos
c.IncludeRepos = *githubIncludeRepos
}
if err = e.ScanGitHub(ctx, sources.NewConfig(github)); err != nil {
logrus.WithError(err).Fatal("Failed to scan Github.")
err = e.ScanGitHub(ctx, *githubScanEndpoint, *githubScanRepos, *githubScanOrgs, *githubScanToken, *githubIncludeForks, filter, *concurrency, *githubIncludeMembers)
if err != nil {
logrus.WithError(err).Fatal("Failed to scan git.")
}
case gitlabScan.FullCommand():
filter, err := common.FilterFromFiles(*gitlabScanIncludePaths, *gitlabScanExcludePaths)
err := e.ScanGitLab(ctx, *gitlabScanEndpoint, *gitlabScanToken, *gitlabScanRepos)
if err != nil {
logrus.WithError(err).Fatal("could not create filter")
}
gitlab := func(c *sources.Config) {
c.Endpoint = *gitlabScanEndpoint
c.Token = *gitlabScanToken
c.Repos = *gitlabScanRepos
c.Filter = filter
}
if err = e.ScanGitLab(ctx, sources.NewConfig(gitlab)); err != nil {
logrus.WithError(err).Fatal("Failed to scan GitLab.")
}
case filesystemScan.FullCommand():
fs := func(c *sources.Config) {
c.Directories = *filesystemDirectories
}
if err = e.ScanFileSystem(ctx, sources.NewConfig(fs)); err != nil {
err := e.ScanFileSystem(ctx, *filesystemDirectories)
if err != nil {
logrus.WithError(err).Fatal("Failed to scan filesystem")
}
case s3Scan.FullCommand():
s3 := func(c *sources.Config) {
c.Key = *s3ScanKey
c.Secret = *s3ScanSecret
c.Buckets = *s3ScanBuckets
}
if err = e.ScanS3(ctx, sources.NewConfig(s3)); err != nil {
err := e.ScanS3(ctx, *s3ScanKey, *s3ScanSecret, *s3ScanCloudEnv, *s3ScanBuckets)
if err != nil {
logrus.WithError(err).Fatal("Failed to scan S3.")
}
case syslogScan.FullCommand():
syslog := func(c *sources.Config) {
c.Address = *syslogAddress
c.Protocol = *syslogProtocol
c.CertPath = *syslogTLSCert
c.KeyPath = *syslogTLSKey
c.Format = *syslogFormat
c.Concurrency = *concurrency
}
if err = e.ScanSyslog(ctx, sources.NewConfig(syslog)); err != nil {
err := e.ScanSyslog(ctx, *syslogAddress, *syslogProtocol, *syslogTLSCert, *syslogTLSKey, *syslogFormat, *concurrency)
if err != nil {
logrus.WithError(err).Fatal("Failed to scan syslog.")
}
}
// asynchronously wait for scanning to finish and cleanup
go e.Finish(ctx)
go e.Finish()
if !*jsonLegacy && !*jsonOut {
fmt.Fprintf(os.Stderr, "🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n")
@@ -312,7 +240,7 @@ func run(state overseer.State) {
switch {
case *jsonLegacy:
output.PrintLegacyJSON(ctx, &r)
output.PrintLegacyJSON(&r)
case *jsonOut:
output.PrintJSON(&r)
default:
@@ -320,7 +248,6 @@ func run(state overseer.State) {
}
}
logrus.Debugf("scanned %d chunks", e.ChunksScanned())
logrus.Debugf("scanned %d bytes", e.BytesScanned())
if *printAvgDetectorTime {
printAverageDetectorTime(e)
-3
View File
@@ -86,9 +86,6 @@ 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
+1 -10
View File
@@ -121,15 +121,6 @@ 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{
@@ -151,7 +142,7 @@ func SaneHttpClient() *http.Client {
return httpClient
}
// SaneHttpClientTimeOut adds a custom timeout for some scanners
//custom timeout for some scanners
func SaneHttpClientTimeOut(timeOutSeconds int64) *http.Client {
httpClient := &http.Client{}
httpClient.Timeout = time.Second * time.Duration(timeOutSeconds)
-47
View File
@@ -1,47 +0,0 @@
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)
}
}
@@ -1,16 +1,18 @@
package sources
package common
import (
"errors"
"fmt"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)
type ChunkFunc func(chunk *Chunk) error
type ChunkFunc func(chunk *sources.Chunk) error
var MatchError = errors.New("chunk doesn't match")
func HandleTestChannel(chunksCh chan *Chunk, cf ChunkFunc) error {
func HandleTestChannel(chunksCh chan *sources.Chunk, cf ChunkFunc) error {
for {
select {
case gotChunk := <-chunksCh:
+10 -2
View File
@@ -2,14 +2,22 @@ package common
import (
"path/filepath"
"github.com/h2non/filetype"
)
var (
KB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15
IGNORED_EXTENSIONS = []string{"mp4", "avi", "mpeg", "mpg", "mov", "wmv", "m4p", "swf", "mp2", "flv", "vob", "webm", "hdv", "3gp", "ogg", "mp3", "wav", "flac", "webp"}
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"}
)
func SkipFile(filename string) bool {
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 {
return true
-45
View File
@@ -1,45 +0,0 @@
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
}
-138
View File
@@ -1,138 +0,0 @@
package context
import (
"context"
"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
}
// Logger returns a structured logger.
func (l logCtx) Logger() logr.Logger {
return l.log
}
// 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)
return logCtx{
log: parent.Logger(),
Context: ctx,
}, 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)
return logCtx{
log: parent.Logger().WithValues("deadline", d),
Context: ctx,
}, 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)
return logCtx{
log: parent.Logger().WithValues("timeout", timeout),
Context: ctx,
}, 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
}
-171
View File
@@ -1,171 +0,0 @@
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")
}
-226
View File
@@ -1,226 +0,0 @@
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"
)
// 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 {
// TODO: Log error.
// This should never happen due to validation.
continue
}
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)
// Create result object and test for verification.
for _, match := range matches {
if common.IsDone(ctx) {
// TODO: Log we're possibly leaving out results.
return results, nil
}
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 isKnownFalsePositive(match) {
continue
}
if !verify {
results = append(results, result)
continue
}
// Verify via webhook.
jsonBody, err := json.Marshal(map[string]map[string][]string{
c.GetName(): match,
})
if err != nil {
continue
}
// 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 results, nil
}
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
}
}
results = append(results, result)
}
return results, 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
}
// 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.
func isKnownFalsePositive(match map[string][]string) bool {
for _, values := range match {
for _, value := range values {
if detectors.IsKnownFalsePositive(value, detectors.DefaultFalsePositives, true) {
return true
}
}
}
return false
}
@@ -1,183 +0,0 @@
package custom_detectors
import (
"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 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 BenchmarkProductIndices(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = productIndices(3, 2, 6)
}
}
-44
View File
@@ -1,44 +0,0 @@
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,
}
}
@@ -1,76 +0,0 @@
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)
})
}
}
-103
View File
@@ -1,103 +0,0 @@
package custom_detectors
import (
"fmt"
"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")
}
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
}
-227
View File
@@ -1,227 +0,0 @@
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,
},
}
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)
}
})
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
func DefaultDecoders() []Decoder {
return []Decoder{
&UTF8{},
&Plain{},
&Base64{},
}
}
+14
View File
@@ -0,0 +1,14 @@
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
}
-44
View File
@@ -1,44 +0,0 @@
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"))
}
-87
View File
@@ -1,87 +0,0 @@
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)
}
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+7 -5
View File
@@ -2,12 +2,14 @@ package abuseipdb
import (
"context"
"io"
"net/http"
"io/ioutil"
// "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"
@@ -56,11 +58,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 := io.ReadAll(res.Body)
bodyBytes, err := ioutil.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 {
@@ -81,5 +83,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -68,5 +68,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -68,5 +68,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -51,7 +51,6 @@ 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 {
@@ -80,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -52,7 +52,6 @@ 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 {
@@ -78,5 +77,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -52,7 +52,6 @@ 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 {
@@ -81,5 +80,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -53,7 +53,6 @@ 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 {
@@ -81,5 +80,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -71,5 +71,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -53,7 +53,6 @@ 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 {
@@ -85,5 +84,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -68,5 +68,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -71,5 +71,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -53,7 +53,6 @@ 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 {
@@ -79,5 +78,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -71,5 +71,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -74,5 +74,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -53,7 +53,6 @@ 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 {
@@ -80,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -72,5 +72,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -51,7 +51,6 @@ 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 {
@@ -78,5 +77,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+2 -3
View File
@@ -84,7 +84,6 @@ 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 {
@@ -104,7 +103,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()
@@ -128,5 +127,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -69,5 +69,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+6 -4
View File
@@ -2,12 +2,14 @@ package allsports
import (
"context"
"io"
"net/http"
"io/ioutil"
// "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"
@@ -56,7 +58,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 := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
continue
}
@@ -75,5 +77,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+3 -4
View File
@@ -2,7 +2,7 @@ package amadeus
import (
"context"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
@@ -52,7 +52,6 @@ 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 {
@@ -66,7 +65,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 := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
continue
}
@@ -86,5 +85,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -52,7 +52,6 @@ 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 {
@@ -79,5 +78,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -52,7 +52,6 @@ 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 {
@@ -80,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -69,5 +69,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+4 -6
View File
@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
@@ -57,13 +57,11 @@ 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 := io.ReadAll(res.Body)
body, errBody := ioutil.ReadAll(res.Body)
var result Response
if errBody == nil {
if err := json.Unmarshal(body, &result); err != nil {
continue
}
json.Unmarshal(body, &result)
if res.StatusCode >= 200 && res.StatusCode < 300 && result.ReturnCode == 0 {
s1.Verified = true
@@ -80,7 +78,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
type Response struct {
+1 -2
View File
@@ -53,7 +53,6 @@ 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 {
@@ -82,5 +81,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -52,7 +52,6 @@ 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 {
@@ -78,5 +77,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -81,5 +81,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -68,5 +68,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
-72
View File
@@ -1,72 +0,0 @@
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
}
-116
View File
@@ -1,116 +0,0 @@
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)
}
}
})
}
}
+1 -2
View File
@@ -54,7 +54,6 @@ 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
@@ -82,5 +81,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -69,5 +69,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -63,7 +63,6 @@ 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)
@@ -90,5 +89,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -73,5 +73,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
-78
View File
@@ -1,78 +0,0 @@
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
}
-116
View File
@@ -1,116 +0,0 @@
package appointedd
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 TestAppointedd_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
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("APPOINTEDD")
inactiveSecret := testSecrets.MustGetField("APPOINTEDD_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 appointedd secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Appointedd,
Verified: true,
},
},
wantErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
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_Appointedd,
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("Appointedd.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("Appointedd.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)
}
}
})
}
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+3 -4
View File
@@ -3,7 +3,7 @@ package apptivo
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
@@ -53,7 +53,6 @@ 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 {
@@ -63,7 +62,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
res, err := client.Do(req)
if err == nil {
bodyBytes, err := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
continue
}
@@ -89,5 +88,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -54,7 +54,6 @@ 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 {
@@ -81,5 +80,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -53,7 +53,6 @@ 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 {
@@ -80,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -69,5 +69,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+3 -3
View File
@@ -3,7 +3,7 @@ package audd
import (
"context"
"fmt"
"io"
"io/ioutil"
"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 := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err == nil {
bodyString := string(bodyBytes)
validResponse := strings.Contains(bodyString, `"status":"success"`)
@@ -79,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -58,7 +58,6 @@ 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 {
@@ -88,5 +87,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+8 -6
View File
@@ -2,12 +2,15 @@ package auth0oauth
import (
"context"
"io"
"net/http"
"net/url"
// "fmt"
// "log"
"regexp"
"strings"
"io/ioutil"
"net/http"
"net/url"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -62,7 +65,6 @@ 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 {
@@ -88,7 +90,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 := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
continue
}
@@ -114,5 +116,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -52,7 +52,6 @@ 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 {
@@ -80,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
@@ -56,7 +56,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
res, err := client.Do(req)
if err == nil {
bodyBytes, err := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
continue
}
@@ -79,5 +79,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -69,5 +69,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
@@ -21,7 +21,7 @@ var (
client = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
// The number prefix increments for every Personal Access Token created.
//The number prefix increments for every Personal Access Token created.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"avaza"}) + `\b([0-9]+-[0-9a-f]{40})\b`)
)
@@ -71,5 +71,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -72,5 +72,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+9 -65
View File
@@ -5,7 +5,6 @@ import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"regexp"
@@ -17,35 +16,10 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type scanner struct {
skipIDs map[string]struct{}
}
type Scanner struct{}
func New(opts ...func(*scanner)) *scanner {
scanner := &scanner{
skipIDs: map[string]struct{}{},
}
for _, opt := range opts {
opt(scanner)
}
return scanner
}
func WithSkipIDs(skipIDs []string) func(*scanner) {
return func(s *scanner) {
ids := map[string]struct{}{}
for _, id := range skipIDs {
ids[id] = struct{}{}
}
s.skipIDs = ids
}
}
// Ensure the scanner satisfies the interface at compile time.
var _ detectors.Detector = (*scanner)(nil)
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
@@ -53,7 +27,7 @@ var (
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
// Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
idPat = regexp.MustCompile(`\b((?:AKIA|ABIA|ACCA|ASIA)[0-9A-Z]{16})\b`)
secretPat = regexp.MustCompile(`[^A-Za-z0-9+\/]{0,1}([A-Za-z0-9+\/]{40})[^A-Za-z0-9+\/]{0,1}`)
secretPat = regexp.MustCompile(`\b([A-Za-z0-9+/]{40})[ \r\n'"\x60]`)
// Hashes, like those for git, do technically match the secret pattern.
// But they are extremely unlikely to be generated as an actual AWS secret.
// So when we find them, if they're not verified, we should ignore the result.
@@ -62,7 +36,7 @@ var (
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s scanner) Keywords() []string {
func (s Scanner) Keywords() []string {
return []string{
"AKIA",
"ABIA",
@@ -85,7 +59,7 @@ func GetHMAC(key []byte, data []byte) []byte {
}
// FromData will find and optionally verify AWS secrets in a given set of bytes.
func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
idMatches := idPat.FindAllStringSubmatch(dataStr, -1)
@@ -97,12 +71,6 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
resIDMatch := strings.TrimSpace(idMatch[1])
if s.skipIDs != nil {
if _, ok := s.skipIDs[resIDMatch]; ok {
continue
}
}
for _, secretMatch := range secretMatches {
if len(secretMatch) != 2 {
continue
@@ -113,7 +81,6 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_AWS,
Raw: []byte(resIDMatch),
Redacted: resIDMatch,
RawV2: []byte(resIDMatch + resSecretMatch),
}
if verify {
@@ -130,7 +97,6 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
if err != nil {
continue
}
req.Header.Set("Accept", "application/json")
// TASK 1: CREATE A CANONICAL REQUEST.
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
@@ -173,19 +139,10 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
res, err := client.Do(req)
if err == nil {
// Response body is unused so close it immediately.
res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
identityInfo := identityRes{}
err := json.NewDecoder(res.Body).Decode(&identityInfo)
if err == nil {
s1.Verified = true
s1.ExtraData = map[string]string{
"account": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account,
"user_id": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID,
"arn": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn,
}
}
res.Body.Close()
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(resSecretMatch, detectors.DefaultFalsePositives, true) {
@@ -236,16 +193,3 @@ func awsCustomCleanResults(results []detectors.Result) []detectors.Result {
}
return out
}
type identityRes struct {
GetCallerIdentityResponse struct {
GetCallerIdentityResult struct {
Account string `json:"Account"`
Arn string `json:"Arn"`
UserID string `json:"UserId"`
} `json:"GetCallerIdentityResult"`
ResponseMetadata struct {
RequestID string `json:"RequestId"`
} `json:"ResponseMetadata"`
} `json:"GetCallerIdentityResponse"`
}
+13 -60
View File
@@ -10,11 +10,11 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
"github.com/kylelemons/godebug/pretty"
)
func TestAWS_FromChunk(t *testing.T) {
@@ -39,14 +39,14 @@ func TestAWS_FromChunk(t *testing.T) {
}
tests := []struct {
name string
s scanner
s Scanner
args args
want []detectors.Result
wantErr bool
}{
{
name: "found, verified",
s: scanner{},
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
@@ -57,18 +57,13 @@ func TestAWS_FromChunk(t *testing.T) {
DetectorType: detectorspb.DetectorType_AWS,
Verified: true,
Redacted: "AKIAWARWQKZNHMZBLY4I",
ExtraData: map[string]string{
"account": "413504919130",
"arn": "arn:aws:iam::413504919130:root",
"user_id": "413504919130",
},
},
},
wantErr: false,
},
{
name: "found, unverified",
s: scanner{},
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
@@ -79,14 +74,13 @@ func TestAWS_FromChunk(t *testing.T) {
DetectorType: detectorspb.DetectorType_AWS,
Verified: false,
Redacted: "AKIAWARWQKZNHMZBLY4I",
ExtraData: nil,
},
},
wantErr: false,
},
{
name: "not found",
s: scanner{},
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
@@ -97,7 +91,7 @@ func TestAWS_FromChunk(t *testing.T) {
},
{
name: "found two, one included for every ID found",
s: scanner{},
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and this is the secret %s", id, secret, inactiveID, inactiveSecret)),
@@ -108,11 +102,6 @@ func TestAWS_FromChunk(t *testing.T) {
DetectorType: detectorspb.DetectorType_AWS,
Verified: true,
Redacted: id,
ExtraData: map[string]string{
"account": "413504919130",
"arn": "arn:aws:iam::413504919130:root",
"user_id": "413504919130",
},
},
{
DetectorType: detectorspb.DetectorType_AWS,
@@ -124,7 +113,7 @@ func TestAWS_FromChunk(t *testing.T) {
},
{
name: "not found, because unverified secret was a hash",
s: scanner{},
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", hash, id)), // The secret would satisfy the regex but be filtered out after not passing validation.
@@ -135,7 +124,7 @@ func TestAWS_FromChunk(t *testing.T) {
},
{
name: "found two, returned both because the active secret for one paired with the inactive ID, despite the hash",
s: scanner{},
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and the secret is this hash %s", id, secret, inactiveID, hash)),
@@ -146,11 +135,6 @@ func TestAWS_FromChunk(t *testing.T) {
DetectorType: detectorspb.DetectorType_AWS,
Verified: true,
Redacted: id,
ExtraData: map[string]string{
"account": "413504919130",
"arn": "arn:aws:iam::413504919130:root",
"user_id": "413504919130",
},
},
{
DetectorType: detectorspb.DetectorType_AWS,
@@ -160,41 +144,10 @@ func TestAWS_FromChunk(t *testing.T) {
},
wantErr: false,
},
{
name: "found, unverified, with leading +",
s: scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", "+HaNv9cTwheDKGJaws/+BMF2GgybQgBWdhcOOdfF", id)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AWS,
Verified: false,
Redacted: "AKIAWARWQKZNHMZBLY4I",
},
},
wantErr: false,
},
{
name: "skipped",
s: scanner{
skipIDs: map[string]struct{}{
"AKIAWARWQKZNHMZBLY4I": {},
},
},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", "+HaNv9cTwheDKGJaws/+BMF2GgybQgBWdhcOOdfF", id)), // the secret would satisfy the regex but not pass validation
verify: true,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := tt.s
s := Scanner{}
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("AWS.FromData() error = %v, wantErr %v", err, tt.wantErr)
@@ -204,9 +157,9 @@ func TestAWS_FromChunk(t *testing.T) {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("AWS.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
@@ -215,7 +168,7 @@ func TestAWS_FromChunk(t *testing.T) {
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := scanner{}
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
+1 -1
View File
@@ -69,5 +69,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -2
View File
@@ -52,7 +52,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Aylien,
Raw: []byte(resMatch),
RawV2: []byte(resMatch + resIdMatch),
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.aylien.com/news/stories", nil)
@@ -79,5 +78,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -83,5 +83,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+1 -1
View File
@@ -70,5 +70,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}
+3 -3
View File
@@ -3,7 +3,7 @@ package baseapiio
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
@@ -57,7 +57,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 := io.ReadAll(res.Body)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
continue
}
@@ -77,5 +77,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
results = append(results, s1)
}
return results, nil
return detectors.CleanResults(results), nil
}

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