Merge branch 'main' of github.com:trufflesecurity/trufflehog into feature/config-verification

This commit is contained in:
Zachary Rice
2024-01-18 13:09:20 -06:00
55 changed files with 3537 additions and 1924 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
with:
go-version: '1.21'
- name: Cosign install
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
uses: sigstore/cosign-installer@9614fae9e5c5eddabb09f90a270fcb487c9f7149 # v3.3.0
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
-7
View File
@@ -13,10 +13,6 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Checkout code
uses: actions/checkout@v4
with:
@@ -26,7 +22,4 @@ jobs:
uses: ./
id: dogfood
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified
+1 -1
View File
@@ -8,7 +8,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .
FROM alpine:3.18
FROM alpine:3.19
RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \
&& rm -rf /var/cache/apk/* && update-ca-certificates
COPY --from=builder /build/trufflehog /usr/bin/trufflehog
+1 -1
View File
@@ -1,4 +1,4 @@
FROM alpine:3.18
FROM alpine:3.19
RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \
&& rm -rf /var/cache/apk/* && update-ca-certificates
+76 -20
View File
@@ -65,6 +65,10 @@ cd trufflehog; go install
# Using installation script
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
# Using installation script, verify checksum signature (requires cosign to be installed)
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -v -b /usr/local/bin
# Using installation script to install a specific version
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin <ReleaseTag like v3.56.0>
```
@@ -103,6 +107,9 @@ Verification steps are as follow:
Replace `{version}` with the downloaded files version
Alternatively, if you are using installation script, pass `-v` option to perform signature verification.
This required Cosign binary to be installed prior to running installation script.
# :rocket: Quick Start
## 1: Scan a repo for only verified secrets
@@ -336,6 +343,62 @@ Exit Codes:
## :octocat: TruffleHog Github Action
### General Usage
```
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Secret Scanning
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
```
In the example config above, we're scanning for live secrets in all PRs and Pushes to `main`. Only code changes in the referenced commits are scanned. If you'd like to scan an entire branch, please see the "Advanced Usage" section below.
### Shallow Cloning
If you're incorporating TruffleHog into a standalone workflow and aren't running any other CI/CD tooling alongside TruffleHog, then we recommend using [Shallow Cloning](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt) to speed up your workflow. Here's an example for how to do it:
```
...
- shell: bash
run: |
if [ "${{ github.event_name }}" == "push" ]; then
echo "depth=$(($(jq length <<< '${{ toJson(github.event.commits) }}') + 2))" >> $GITHUB_ENV
echo "branch=${{ github.ref_name }}" >> $GITHUB_ENV
fi
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "depth=$((${{ github.event.pull_request.commits }}+2))" >> $GITHUB_ENV
echo "branch=${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV
fi
- uses: actions/checkout@v3
with:
ref: ${{env.branch}}
fetch-depth: ${{env.depth}}
- uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
...
```
Depending on the event type (push or PR), we calculate the number of commits present. Then we add 2, so that we can reference a base commit before our code changes. We pass that integer value to the `fetch-depth` flag in the checkout action in addition to the relevant branch. Now our checkout process should be much shorter.
### Advanced Usage
```yaml
- name: TruffleHog
uses: trufflesecurity/trufflehog@main
@@ -350,34 +413,27 @@ Exit Codes:
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
any results are found.
If you'd like to specify specific `base` and `head` refs, you can use the `base` argument (`--since-commit` flag in TruffleHog CLI) and the `head` argument (`--branch` flag in the TruffleHog CLI). We only recommend using these arguments for very specific use cases, where the default behavior does not work.
For example, to scan the contents of pull requests you could use the following workflow:
```yaml
name: TruffleHog Secrets Scan
on: [pull_request]
jobs:
TruffleHog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: TruffleHog OSS
#### Advanced Usage: Scan entire branch
```
- name: scan-push
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --debug --only-verified
base: ""
head: ${{ github.ref_name }}
extra_args: --only-verified
```
## Pre-commit Hook
Trufflehog can be used in a pre-commit hook to prevent credentials from leaking before they ever leave your computer.
**Key Usage Note:**
- **For optimal hook efficacy, execute `git add` followed by `git commit` separately.** This ensures Trufflehog analyzes all intended changes.
- **Avoid using `git commit -am`, as it might bypass pre-commit hook execution for unstaged modifications.**
An example `.pre-commit-config.yaml` is provided (see [pre-commit.com](https://pre-commit.com/) for installation).
```yaml
+72 -15
View File
@@ -1,11 +1,12 @@
name: 'TruffleHog OSS'
description: 'Scan Github Actions with TruffleHog'
description: 'Scan Github Actions with TruffleHog.'
author: Truffle Security Co. <[email protected]>
inputs:
path:
description: Repository path
required: true
required: false
default: "./"
base:
description: Start scanning from here (usually main branch).
required: false
@@ -20,17 +21,73 @@ inputs:
branding:
icon: "shield"
color: "green"
runs:
using: "docker"
image: "docker://ghcr.io/trufflesecurity/trufflehog:latest"
args:
- git
- file://${{ inputs.path }}
- --since-commit
- ${{ inputs.base }}
- --branch
- ${{ inputs.head }}
- --fail
- --no-update
- --github-actions
- ${{ inputs.extra_args }}
using: "composite"
steps:
- shell: bash
env:
REPO_PATH: ${{ inputs.path }}
BASE: ${{ inputs.base }}
HEAD: ${{ inputs.head }}
ARGS: ${{ inputs.extra_args }}
COMMITS: ${{ toJson(github.event.commits) }}
run: |
##########################################
## ADVANCED USAGE ##
## Scan by BASE & HEAD user inputs ##
## If BASE == HEAD, exit with error ##
##########################################
if [ -n "$BASE" ] || [ -n "$HEAD" ]; then
if [ -n "$BASE" ]; then
base_commit=$(git rev-parse "$BASE" 2>/dev/null) || true
else
base_commit=""
fi
if [ -n "$HEAD" ]; then
head_commit=$(git rev-parse "$HEAD" 2>/dev/null) || true
else
head_commit=""
fi
if [ $base_commit == $head_commit ] ; then
echo "::error::BASE and HEAD commits are the same. TruffleHog won't scan anything. Please see documentation (https://github.com/trufflesecurity/trufflehog#octocat-trufflehog-github-action)."
exit 1
fi
##########################################
## Scan commits based on event type ##
##########################################
else
if [ "${{ github.event_name }}" == "push" ]; then
COMMIT_LENGTH=$(printenv COMMITS | jq length)
if [ $COMMIT_LENGTH == "0" ]; then
echo "No commits to scan"
exit 0
fi
HEAD=${{ github.event.after }}
if [ ${{ github.event.before }} == "0000000000000000000000000000000000000000" ]; then
BASE=$(git rev-parse $HEAD~$COMMIT_LENGTH)
else
BASE=${{ github.event.before }}
fi
elif [ "${{ github.event_name }}" == "workflow_dispatch" ] || [ "${{ github.event_name }}" == "schedule" ]; then
BASE=""
HEAD=""
elif [ "${{ github.event_name }}" == "pull_request" ]; then
BASE=${{github.event.pull_request.base.sha}}
HEAD=${{github.event.pull_request.head.sha}}
fi
fi
##########################################
## Run TruffleHog ##
##########################################
docker run --rm -v "$REPO_PATH":/tmp \
ghcr.io/trufflesecurity/trufflehog:latest \
git file:///tmp/ \
--since-commit \
${BASE:-''} \
--branch \
${HEAD:-''} \
--fail \
--no-update \
--github-actions \
${ARGS:-''}
+12 -11
View File
@@ -6,17 +6,17 @@ replace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.2
require (
cloud.google.com/go/secretmanager v1.11.4
cloud.google.com/go/storage v1.35.1
cloud.google.com/go/storage v1.36.0
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1
github.com/BobuSumisu/aho-corasick v1.0.3
github.com/TheZeroSlave/zapsentry v1.19.0
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/aws/aws-sdk-go v1.48.12
github.com/aws/aws-sdk-go v1.49.19
github.com/aymanbagabas/go-osc52 v1.2.2
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.8.0
github.com/bradleyfalzon/ghinstallation/v2 v2.9.0
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/glamour v0.6.0
@@ -28,9 +28,10 @@ require (
github.com/envoyproxy/protoc-gen-validate v1.0.2
github.com/fatih/color v1.16.0
github.com/felixge/fgprof v0.9.3
github.com/gabriel-vasile/mimetype v1.4.3
github.com/getsentry/sentry-go v0.25.0
github.com/go-errors/errors v1.5.1
github.com/go-git/go-git/v5 v5.10.1
github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-logr/logr v1.3.0
github.com/go-logr/zapr v1.3.0
@@ -56,7 +57,7 @@ require (
github.com/lrstanley/bubblezone v0.0.0-20230911164824-e3824f1adde9
github.com/marusama/semaphore/v2 v2.5.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.18
github.com/mattn/go-sqlite3 v1.14.19
github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/mitchellh/go-ps v1.0.0
github.com/muesli/reflow v0.3.0
@@ -75,8 +76,8 @@ require (
go.mongodb.org/mongo-driver v1.12.1
go.uber.org/mock v0.3.0
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.16.0
golang.org/x/exp v0.0.0-20231127185646-65229373498e
golang.org/x/crypto v0.17.0
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e
golang.org/x/net v0.19.0
golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.5.0
@@ -137,7 +138,7 @@ require (
github.com/bodgit/sevenzip v1.4.5 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/couchbase/gocbcore/v10 v10.3.0 // indirect
@@ -154,10 +155,9 @@ require (
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
@@ -172,6 +172,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v23.1.21+incompatible // indirect
github.com/google/go-github/v56 v56.0.0 // indirect
github.com/google/go-github/v57 v57.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/google/s2a-go v0.1.7 // indirect
+28 -12
View File
@@ -28,6 +28,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@@ -68,6 +70,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g=
github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -104,6 +108,10 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go v1.48.12 h1:n+eGzflzzvYubu2cOjqpVll7lF+Ci0ThyCpg5kzfzbo=
github.com/aws/aws-sdk-go v1.48.12/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.18 h1:g/iMXkfXeJQ7MvnLwroxWsTTNkHtdVJGxIgrAIEG62M=
github.com/aws/aws-sdk-go v1.49.18/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.49.19 h1:oZryiqeQpeJsIcAmZlp86duMu/s/DJ43qyfwa51qmLg=
github.com/aws/aws-sdk-go v1.49.19/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
@@ -154,19 +162,18 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c h1:tSME5FDS02qQll3JYodI6RZR/g4EKOHApGv1wMZT+Z0=
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c/go.mod h1:+sCc6hztur+oZCLOsNk6wCCy+GLrnSNHSRmTnnL+8iQ=
github.com/bill-rich/sevenzip v0.0.0-20231205215127-9521c543efac h1:7jDHN8fn8cNf1ibps8CrHivEM7GtXpsuaJeD5CWkw/Y=
github.com/bill-rich/sevenzip v0.0.0-20231205215127-9521c543efac/go.mod h1:aHY9hBGPQUQIimuGB0H+LJPqCI/68ZZFxRRab0gk/nU=
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb h1:9v7Bzlg+1EBYi2IYcUmOwHReBEfqBbYIj3ZCi9cIe1Q=
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb/go.mod h1:EkOqCuelvo7DY8vCOoZ09p7pHvAK9B1PHI9GeM4Rdxc=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.4.3/go.mod h1:F8n3+0CwbdxqmNy3wFeOAtanza02Ur66AGfs/hbYblI=
github.com/bodgit/sevenzip v1.4.5 h1:HFJQ+nbjppfyf2xbQEJBbmVo+o2kTg1FXV4i7YOx87s=
github.com/bodgit/sevenzip v1.4.5/go.mod h1:LAcAg/UQzyjzCQSGBPZFYzoiHMfT6Gk+3tMSjUk3foY=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0 h1:yUmoVv70H3J4UOqxqsee39+KlXxNEDfTbAp8c/qULKk=
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0/go.mod h1:fmPmvCiBWhJla3zDv9ZTQSZc8AbwyRnGW1yg5ep1Pcs=
github.com/bradleyfalzon/ghinstallation/v2 v2.9.0 h1:HmxIYqnxubRYcYGRc5v3wUekmo5Wv2uX3gukmWJ0AFk=
github.com/bradleyfalzon/ghinstallation/v2 v2.9.0/go.mod h1:wmkTDJf8CmVypxE8ijIStFnKoTa6solK5QfdmJrP9KI=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -183,8 +190,9 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coinbase/waas-client-library-go v1.0.8 h1:AdbTmBQpsSUx947GZd5/68BhNBw1CSwTfE2PcnVy3Ao=
github.com/coinbase/waas-client-library-go v1.0.8/go.mod h1:RVKozprfdfMiK92ATZUWHRs0EFGHQj4rbEJjzzZzI1I=
@@ -234,8 +242,8 @@ github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNk
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@@ -257,6 +265,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
@@ -271,8 +281,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk=
github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -366,6 +376,8 @@ github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5Px
github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg=
github.com/google/go-github/v56 v56.0.0 h1:TysL7dMa/r7wsQi44BjqlwaHvwlFlqkK8CtBWCX3gb4=
github.com/google/go-github/v56 v56.0.0/go.mod h1:D8cdcX98YWJvi7TLo7zM4/h8ZTx6u6fwGEkCdisopo0=
github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
@@ -513,6 +525,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
@@ -571,8 +585,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs=
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@@ -726,8 +738,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -738,6 +750,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+1
View File
@@ -203,6 +203,7 @@ func main() {
}
},
true,
false,
)
logger.Info("scanning repo", "repo", r)
+36 -13
View File
@@ -5,10 +5,12 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/alecthomas/kingpin/v2"
"github.com/felixge/fgprof"
@@ -55,6 +57,7 @@ var (
noUpdate = cli.Flag("no-update", "Don't check for updates.").Bool()
fail = cli.Flag("fail", "Exit with code 183 if results are found.").Bool()
verifiers = cli.Flag("verifier", "Set custom verification endpoints.").StringMap()
customVerifiersOnly = cli.Flag("custom-verifiers-only", "Only use custom verification endpoints.").Bool()
archiveMaxSize = cli.Flag("archive-max-size", "Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)").Bytes()
archiveMaxDepth = cli.Flag("archive-max-depth", "Maximum depth of archive to scan.").Int()
archiveTimeout = cli.Flag("archive-timeout", "Maximum time to spend extracting an archive.").Duration()
@@ -216,17 +219,39 @@ func main() {
if err != nil {
logFatal(err, "error occurred with trufflehog updater 🐷")
}
ctx := context.Background()
go cleantemp.RunCleanupLoop(ctx)
}
func run(state overseer.State) {
ctx := context.Background()
ctx, cancel := context.WithCancelCause(context.Background())
defer cancel(nil)
go func() {
if err := cleantemp.CleanTempArtifacts(ctx); err != nil {
ctx.Logger().Error(err, "error cleaning temporary artifacts")
}
}()
logger := ctx.Logger()
logFatal := logFatalFunc(logger)
killSignal := make(chan os.Signal, 1)
signal.Notify(killSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
<-killSignal
logger.Info("Received signal, shutting down.")
cancel(fmt.Errorf("canceling context due to signal"))
if err := cleantemp.CleanTempArtifacts(ctx); err != nil {
logger.Error(err, "error cleaning temporary artifacts")
} else {
logger.Info("cleaned temporary artifacts")
}
time.Sleep(time.Second * 10)
logger.Info("10 seconds elapsed. Forcing shutdown.")
os.Exit(0)
}()
logger.V(2).Info(fmt.Sprintf("trufflehog %s", version.BuildVersion))
if *githubScanToken != "" {
@@ -341,8 +366,9 @@ func run(state overseer.State) {
"detector", id,
)
}
// TODO: Add flag to ignore the default endpoint.
urls = append(urls, customizer.DefaultEndpoint())
if !*customVerifiersOnly || len(urls) == 0 {
urls = append(urls, customizer.DefaultEndpoint())
}
if err := customizer.SetEndpoints(urls...); err != nil {
logFatal(err, "failed configuring custom endpoint for detector", "detector", id)
}
@@ -446,10 +472,6 @@ func run(state overseer.State) {
logFatal(err, "Failed to scan GitLab.")
}
case filesystemScan.FullCommand():
filter, err := common.FilterFromFiles(*filesystemScanIncludePaths, *filesystemScanExcludePaths)
if err != nil {
logFatal(err, "could not create filter")
}
if len(*filesystemDirectories) > 0 {
ctx.Logger().Info("--directory flag is deprecated, please pass directories as arguments")
}
@@ -457,8 +479,9 @@ func run(state overseer.State) {
paths = append(paths, *filesystemPaths...)
paths = append(paths, *filesystemDirectories...)
cfg := sources.FilesystemConfig{
Paths: paths,
Filter: filter,
Paths: paths,
IncludePathsFile: *filesystemScanIncludePaths,
ExcludePathsFile: *filesystemScanExcludePaths,
}
if err = e.ScanFileSystem(ctx, cfg); err != nil {
logFatal(err, "Failed to scan filesystem")
+3 -3
View File
@@ -4,9 +4,9 @@ package cache
// Cache is used to store key/value pairs.
type Cache interface {
// Set stores the given key/value pair.
Set(string, string)
Set(string, any)
// Get returns the value for the given key and a boolean indicating if the key was found.
Get(string) (string, bool)
Get(string) (any, bool)
// Exists returns true if the given key exists in the cache.
Exists(string) bool
// Delete the given key from the cache.
@@ -18,7 +18,7 @@ type Cache interface {
// Keys returns all keys in the cache.
Keys() []string
// Values returns all values in the cache.
Values() []string
Values() []any
// Contents returns all keys in the cache encoded as a string.
Contents() string
}
+54 -29
View File
@@ -5,52 +5,77 @@ import (
"time"
"github.com/patrickmn/go-cache"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
const (
expirationInterval = 12 * time.Hour
purgeInterval = 13 * time.Hour
defaultExpiration = cache.DefaultExpiration
defaultExpirationInterval = 12 * time.Hour
defaultPurgeInterval = 13 * time.Hour
defaultExpiration = cache.DefaultExpiration
)
// Cache is a wrapper around the go-cache library.
// Cache wraps the go-cache library to provide an in-memory key-value store.
type Cache struct {
c *cache.Cache
c *cache.Cache
expiration time.Duration
purgeInterval time.Duration
}
// New constructs a new in-memory cache.
func New() *Cache {
c := cache.New(expirationInterval, purgeInterval)
return &Cache{c: c}
// CacheOption defines a function type used for configuring a Cache.
type CacheOption func(*Cache)
// WithExpirationInterval returns a CacheOption to set the expiration interval of cache items.
// The interval determines the duration a cached item remains in the cache before it is expired.
func WithExpirationInterval(interval time.Duration) CacheOption {
return func(c *Cache) { c.expiration = interval }
}
// WithPurgeInterval returns a CacheOption to set the interval at which the cache purges expired items.
// Regular purging helps in freeing up memory by removing stale entries.
func WithPurgeInterval(interval time.Duration) CacheOption {
return func(c *Cache) { c.purgeInterval = interval }
}
// New constructs a new in-memory cache instance with optional configurations.
// By default, it sets the expiration and purge intervals to 12 and 13 hours, respectively.
// These defaults can be overridden using the functional options: WithExpirationInterval and WithPurgeInterval.
func New(opts ...CacheOption) *Cache {
return NewWithData(nil, opts...)
}
// CacheEntry represents a single entry in the cache, consisting of a key and its corresponding value.
type CacheEntry struct {
// Key is the unique identifier for the entry.
Key string
// Value is the data stored in the entry.
Value any
}
// NewWithData constructs a new in-memory cache with existing data.
func NewWithData(ctx context.Context, data []string) *Cache {
ctx.Logger().V(3).Info("Loading cache", "num-items", len(data))
items := make(map[string]cache.Item, len(data))
for _, d := range data {
items[d] = cache.Item{Object: d, Expiration: int64(defaultExpiration)}
// It also accepts CacheOption parameters to override default configuration values.
func NewWithData(data []CacheEntry, opts ...CacheOption) *Cache {
instance := &Cache{expiration: defaultExpirationInterval, purgeInterval: defaultPurgeInterval}
for _, opt := range opts {
opt(instance)
}
c := cache.NewFrom(expirationInterval, purgeInterval, items)
return &Cache{c: c}
// Convert data slice to map required by go-cache.
items := make(map[string]cache.Item, len(data))
for _, d := range data {
items[d.Key] = cache.Item{Object: d.Value, Expiration: int64(defaultExpiration)}
}
instance.c = cache.NewFrom(instance.expiration, instance.purgeInterval, items)
return instance
}
// Set adds a key-value pair to the cache.
func (c *Cache) Set(key, value string) {
func (c *Cache) Set(key string, value any) {
c.c.Set(key, value, defaultExpiration)
}
// Get returns the value for the given key.
func (c *Cache) Get(key string) (string, bool) {
res, ok := c.c.Get(key)
if !ok {
return "", ok
}
return res.(string), ok
func (c *Cache) Get(key string) (any, bool) {
return c.c.Get(key)
}
// Exists returns true if the given key exists in the cache.
@@ -85,11 +110,11 @@ func (c *Cache) Keys() []string {
}
// Values returns all values in the cache.
func (c *Cache) Values() []string {
func (c *Cache) Values() []any {
items := c.c.Items()
res := make([]string, 0, len(items))
res := make([]any, 0, len(items))
for _, v := range items {
res = append(res, v.Object.(string))
res = append(res, v.Object)
}
return res
}
+8 -6
View File
@@ -7,8 +7,6 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
func TestCache(t *testing.T) {
@@ -34,7 +32,7 @@ func TestCache(t *testing.T) {
// Test delete.
c.Delete("key1")
v, ok = c.Get("key1")
if ok || v != "" {
if ok || v != nil {
t.Fatalf("Unexpected value for key1 after delete: %v, %v", v, ok)
}
@@ -42,7 +40,7 @@ func TestCache(t *testing.T) {
c.Set("key10", "key10")
c.Clear()
v, ok = c.Get("key10")
if ok || v != "" {
if ok || v != nil {
t.Fatalf("Unexpected value for key10 after clear: %v, %v", v, ok)
}
@@ -60,7 +58,10 @@ func TestCache(t *testing.T) {
}
// Test getting only the values.
vals := c.Values()
vals := make([]string, 0, c.Count())
for _, v := range c.Values() {
vals = append(vals, v.(string))
}
sort.Strings(vals)
sort.Strings(values)
if !cmp.Equal(values, vals) {
@@ -82,7 +83,8 @@ func TestCache(t *testing.T) {
}
func TestCache_NewWithData(t *testing.T) {
c := NewWithData(logContext.Background(), []string{"key1", "key2", "key3"})
data := []CacheEntry{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}}
c := NewWithData(data)
// Test the count.
if c.Count() != 3 {
+32 -40
View File
@@ -2,12 +2,12 @@ package cleantemp
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/mitchellh/go-ps"
@@ -63,65 +63,57 @@ func CleanTempArtifacts(ctx logContext.Context) error {
}
}
tempDir := os.TempDir()
artifacts, err := os.ReadDir(tempDir)
if err != nil {
return fmt.Errorf("error reading temp dir: %w", err)
if len(pids) == 0 {
ctx.Logger().V(5).Info("No trufflehog processes were found")
return nil
}
for _, artifact := range artifacts {
if trufflehogRE.MatchString(artifact.Name()) {
tempDir := os.TempDir()
dir, err := os.Open(tempDir)
if err != nil {
return fmt.Errorf("error opening temp dir: %w", err)
}
defer dir.Close()
for {
entries, err := dir.ReadDir(1) // read only one entry
if err != nil {
if err == io.EOF {
break
}
continue
}
entry := entries[0]
if trufflehogRE.MatchString(entry.Name()) {
// Mark these artifacts initially as ones that should be deleted.
shouldDelete := true
// Check if the name matches any live PIDs.
// Potential race condition here if a PID is started and creates tmp data after the initial check.
for _, pidval := range pids {
if strings.Contains(artifact.Name(), fmt.Sprintf("-%s-", pidval)) {
if strings.Contains(entry.Name(), fmt.Sprintf("-%s-", pidval)) {
shouldDelete = false
break
}
}
if shouldDelete {
artifactPath := filepath.Join(tempDir, artifact.Name())
var err error
if artifact.IsDir() {
err = os.RemoveAll(artifactPath)
path := filepath.Join(tempDir, entry.Name())
isDir := entry.IsDir()
if isDir {
err = os.RemoveAll(path)
} else {
err = os.Remove(artifactPath)
err = os.Remove(path)
}
if err != nil {
return fmt.Errorf("Error deleting temp artifact: %s", artifactPath)
return fmt.Errorf("error deleting temp artifact (dir: %v) %s: %w", isDir, path, err)
}
ctx.Logger().Info("Deleted orphaned temp artifact", "artifact", artifactPath)
ctx.Logger().V(4).Info("Deleted orphaned temp artifact", "artifact", path)
}
}
}
return nil
}
// RunCleanupLoop runs a loop that cleans up orphaned directories every 15 seconds.
func RunCleanupLoop(ctx logContext.Context) {
err := CleanTempArtifacts(ctx)
if err != nil {
ctx.Logger().Error(err, "Error cleaning up orphaned directories ")
}
const cleanupLoopInterval = 15 * time.Second
ticker := time.NewTicker(cleanupLoopInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := CleanTempArtifacts(ctx); err != nil {
ctx.Logger().Error(err, "error cleaning up orphaned directories")
}
case <-ctx.Done():
ctx.Logger().Info("Cleanup loop exiting due to context cancellation")
return
}
}
}
+4
View File
@@ -28,6 +28,7 @@ var (
"wav",
"flac",
"webp",
"pdf",
// images
"png",
@@ -79,6 +80,9 @@ var (
"vxd": {}, // Virtual device driver in Windows
"sfx": {}, // Self-extracting archive
"bundle": {}, // Mac OS X application bundle
"pyo": {}, // Compiled Python file
"pyc": {}, // Compiled Python file
"sym": {}, // Symbolic link, Unix/Linux
}
)
+28 -7
View File
@@ -43,6 +43,8 @@ type userRes struct {
Name string `json:"name"`
Company string `json:"company"`
UserURL string `json:"html_url"`
// Included in GitHub Enterprise Server.
LdapDN string `json:"ldap_dn"`
}
// Keywords are used for efficiently pre-filtering chunks.
@@ -77,12 +79,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
client := common.SaneHttpClient()
// https://developer.github.com/v3/users/#get-the-authenticated-user
for _, url := range s.Endpoints(s.DefaultEndpoint()) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/user", url), nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/user", url), nil)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json; charset=utf-8")
req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
res, err := client.Do(req)
if err == nil {
if res.StatusCode >= 200 && res.StatusCode < 300 {
@@ -94,10 +97,28 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.ExtraData["username"] = userResponse.Login
s1.ExtraData["url"] = userResponse.UserURL
s1.ExtraData["account_type"] = userResponse.Type
s1.ExtraData["site_admin"] = fmt.Sprintf("%t", userResponse.SiteAdmin)
s1.ExtraData["name"] = userResponse.Name
s1.ExtraData["company"] = userResponse.Company
s1.ExtraData["scopes"] = res.Header.Get("X-OAuth-Scopes")
if userResponse.SiteAdmin {
s1.ExtraData["site_admin"] = "true"
}
if userResponse.Name != "" {
s1.ExtraData["name"] = userResponse.Name
}
if userResponse.Company != "" {
s1.ExtraData["company"] = userResponse.Company
}
if userResponse.LdapDN != "" {
s1.ExtraData["ldap_dn"] = userResponse.LdapDN
}
// GitHub does not seem to consistently return this header.
scopes := res.Header.Get("X-OAuth-Scopes")
if scopes != "" {
s1.ExtraData["scopes"] = scopes
}
expiry := res.Header.Get("github-authentication-token-expiration")
if expiry != "" {
s1.ExtraData["expires_at"] = expiry
}
}
}
} else {
+27 -7
View File
@@ -39,6 +39,8 @@ type userRes struct {
Name string `json:"name"`
Company string `json:"company"`
UserURL string `json:"html_url"`
// Included in GitHub Enterprise Server.
LdapDN string `json:"ldap_dn"`
}
// Keywords are used for efficiently pre-filtering chunks.
@@ -78,12 +80,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
client := common.SaneHttpClient()
// https://developer.github.com/v3/users/#get-the-authenticated-user
for _, url := range s.Endpoints(s.DefaultEndpoint()) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/user", url), nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/user", url), nil)
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/json; charset=utf-8")
req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
res, err := client.Do(req)
if err == nil {
if res.StatusCode >= 200 && res.StatusCode < 300 {
@@ -98,10 +100,28 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.ExtraData["username"] = userResponse.Login
s1.ExtraData["url"] = userResponse.UserURL
s1.ExtraData["account_type"] = userResponse.Type
s1.ExtraData["site_admin"] = fmt.Sprintf("%t", userResponse.SiteAdmin)
s1.ExtraData["name"] = userResponse.Name
s1.ExtraData["company"] = userResponse.Company
s1.ExtraData["scopes"] = res.Header.Get("X-OAuth-Scopes")
if userResponse.SiteAdmin {
s1.ExtraData["site_admin"] = "true"
}
if userResponse.Name != "" {
s1.ExtraData["name"] = userResponse.Name
}
if userResponse.Company != "" {
s1.ExtraData["company"] = userResponse.Company
}
if userResponse.LdapDN != "" {
s1.ExtraData["ldap_dn"] = userResponse.LdapDN
}
// GitHub does not seem to consistently return this header.
scopes := res.Header.Get("X-OAuth-Scopes")
if scopes != "" {
s1.ExtraData["scopes"] = scopes
}
expiry := res.Header.Get("github-authentication-token-expiration")
if expiry != "" {
s1.ExtraData["expires_at"] = expiry
}
}
}
}
+23 -8
View File
@@ -23,13 +23,13 @@ var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\bhf_[a-zA-Z0-9]{34}\b`)
keyPat = regexp.MustCompile(`\b(?:hf_|api_org_)[a-zA-Z0-9]{34}\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{"huggingface", "hugging_face", "hf"} // Huggingface docs occasionally use "hf" instead of "huggingface"
return []string{"hf_", "api_org_"} // Huggingface docs occasionally use "hf" instead of "huggingface"
}
// FromData will find and optionally verify Huggingface secrets in a given set of bytes.
@@ -92,15 +92,29 @@ func (s Scanner) verifyResult(ctx context.Context, apiKey string) (bool, map[str
return true, nil, err
}
t := whoamiRes.Auth.AccessToken
var tokenInfo string
switch {
case whoamiRes.Auth.AccessToken.DisplayName != "" || whoamiRes.Auth.AccessToken.Role != "":
// hf_xxxx token
t := whoamiRes.Auth.AccessToken
tokenInfo = fmt.Sprintf("%s (%s)", t.DisplayName, t.Role)
case whoamiRes.Auth.Type != "":
// api_org_xxxx token
tokenInfo = whoamiRes.Auth.Type
default:
tokenInfo = "Unknown Token Type"
}
extraData := map[string]string{
"Username": whoamiRes.Name,
"Email": whoamiRes.Email,
"Token": fmt.Sprintf("%s (%s)", t.DisplayName, t.Role),
"Token": tokenInfo,
}
// Condense a list of organizations + roles.
var orgs []string
orgs := make([]string, 0, len(whoamiRes.Organizations))
for _, org := range whoamiRes.Organizations {
orgs = append(orgs, fmt.Sprintf("%s:%s", org.Name, org.Role))
}
@@ -136,7 +150,8 @@ type organization struct {
type auth struct {
AccessToken struct {
DisplayName string `json:"displayName"`
Role string `json:"role"`
} `json:"accessToken"`
DisplayName string `json:"displayName,omitempty"`
Role string `json:"role,omitempty"`
} `json:"accessToken,omitempty"`
Type string `json:"type,omitempty"`
}
+36 -20
View File
@@ -12,16 +12,18 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct{}
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time
var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"parseur"}) + `\b([a-f0-9]{40})\b`)
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"parseur[^il]"}) + `\b([a-f0-9]{40})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -35,35 +37,30 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Parseur,
Raw: []byte(resMatch),
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.parseur.com/", nil)
if err != nil {
continue
if s.client == nil {
s.client = defaultClient
}
req.Header.Add("Authorization", fmt.Sprintf("Token %s", 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
}
}
isVerified, verificationErr := verifyResult(ctx, s.client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
if !s1.Verified {
// 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
}
}
@@ -73,6 +70,25 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}
func verifyResult(ctx context.Context, client *http.Client, token string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.parseur.com/", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Token %s", token))
res, err := client.Do(req)
if err != nil {
return false, err
}
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
return true, nil
}
return false, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Parseur
}
+105
View File
@@ -7,12 +7,117 @@ import (
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestParseur_Pattern(t *testing.T) {
tests := []struct {
name string
data string
shouldMatch bool
match string
}{
// True positives
{
name: "valid",
data: `const parseurToken = "6813a07afc6b4ed35518635c6fb70abf4e721962";`,
shouldMatch: true,
match: "6813a07afc6b4ed35518635c6fb70abf4e721962",
},
// This technically isn't valid but shouldn't be excluded based on the current pattern.
{
name: "valid",
data: `commit 6813a07afc6b4ed35518635c6fb70abf4e721962
Author: Stéphane Borel <[email protected]>
Date: Thu Dec 30 13:59:59 1999 +0000
* Modifications de quelques erreurs sur le parseur
commit 2c65bd981d308d264aa0c07083b2bc914905deb3`,
shouldMatch: true,
match: "2c65bd981d308d264aa0c07083b2bc914905deb3",
},
// False positives
{
name: `invalid_parseuri_package.json`,
data: `{
"dist": {
"shasum": "80204a50d4dbb779bfdc6ebe2778d90e4bce320a",
"tarball": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz"
},
"gitHead": "792c9a63162a4484eb6b4f95fc611ccf224a24b6",`,
shouldMatch: false,
},
// https://github.com/airalab/airapkgs/blob/cb3f8021303f79345f65b5328b75117044bde852/pkgs/servers/mesh/meshviewer/yarn.nix#L6066
{
name: `invalid_parseuri_nix`,
data: `
{
name = "parseuri-0.0.5.tgz";
path = fetchurl {
name = "parseuri-0.0.5.tgz";
url = "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz";
sha1 = "80204a50d4dbb779bfdc6ebe2778d90e4bce320a";
};
}`,
shouldMatch: false,
},
{
name: `invalid_parseurl_yarn`,
data: `parseurl@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"`,
shouldMatch: false,
},
// https://github.com/tolerious/django-wechat/blob/18f3f2d5d8377c7dde8700afc5977861c8488b68/django_weixin/Sample.py#L30
{
name: `invalid_parseurl_func`,
data: `#sVerifyMsgSig=HttpUtils.ParseUrl("msg_signature")
sVerifyMsgSig="5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"`,
shouldMatch: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := Scanner{}
results, err := s.FromData(context.Background(), false, []byte(test.data))
if err != nil {
t.Errorf("Parseur.FromData() error = %v", err)
return
}
if test.shouldMatch {
if len(results) == 0 {
t.Errorf("%s: did not receive a match for '%v' when one was expected", test.name, test.data)
return
}
expected := test.data
if test.match != "" {
expected = test.match
}
result := results[0]
resultData := string(result.Raw)
if resultData != expected {
t.Errorf("%s: did not receive expected match.\n\texpected: '%s'\n\t actual: '%s'", test.name, expected, resultData)
return
}
} else {
if len(results) > 0 {
t.Errorf("%s: received a match for '%v' when one wasn't wanted", test.name, test.data)
return
}
}
})
}
}
func TestParseur_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
+254
View File
@@ -0,0 +1,254 @@
package postgres
import (
"context"
"database/sql"
"fmt"
"net/url"
"regexp"
"strings"
"time"
_ "github.com/lib/pq" // PostgreSQL driver
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
const (
defaultPort = "5432"
defaultHost = "localhost"
)
var (
_ detectors.Detector = (*Scanner)(nil)
uriPattern = regexp.MustCompile(`\b(?i)postgresql://[\S]+\b`)
hostnamePattern = regexp.MustCompile(`(?i)(?:host|server|address).{0,40}?(\b[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\b)`)
portPattern = regexp.MustCompile(`(?i)(?:port|p).{0,40}?(\b[0-9]{1,5}\b)`)
usernamePattern = regexp.MustCompile(`(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:'"\s]{4,40})`)
passwordPattern = regexp.MustCompile(`(?im)(?:pass)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:'"\s]{4,40})`)
)
type Scanner struct{}
func (s Scanner) Keywords() []string {
return []string{"postgres", "psql", "pghost"}
}
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {
var results []detectors.Result
var pgURLs []url.URL
pgURLs = append(pgURLs, findUriMatches(string(data)))
pgURLs = append(pgURLs, findComponentMatches(verify, string(data))...)
for _, pgURL := range pgURLs {
if pgURL.User == nil {
continue
}
username := pgURL.User.Username()
password, _ := pgURL.User.Password()
hostport := pgURL.Host
result := detectors.Result{
DetectorType: detectorspb.DetectorType_Postgres,
Raw: []byte(hostport + username + password),
RawV2: []byte(hostport + username + password),
}
if verify {
timeoutInSeconds := getDeadlineInSeconds(ctx)
isVerified, verificationErr := verifyPostgres(&pgURL, timeoutInSeconds)
result.Verified = isVerified
result.SetVerificationError(verificationErr, password)
}
if !result.Verified && detectors.IsKnownFalsePositive(password, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, result)
}
return results, nil
}
func getDeadlineInSeconds(ctx context.Context) int {
deadline, ok := ctx.Deadline()
if !ok {
// Context does not have a deadline
return 0
}
duration := time.Until(deadline)
return int(duration.Seconds())
}
func findUriMatches(dataStr string) url.URL {
var pgURL url.URL
for _, uri := range uriPattern.FindAllString(dataStr, -1) {
pgURL, err := url.Parse(uri)
if err != nil {
continue
}
if pgURL.User != nil {
return *pgURL
}
}
return pgURL
}
// check if postgres is running
func postgresRunning(hostname, port string) bool {
connStr := fmt.Sprintf("host=%s port=%s sslmode=disable", hostname, port)
db, err := sql.Open("postgres", connStr)
if err != nil {
return false
}
defer db.Close()
return true
}
func findComponentMatches(verify bool, dataStr string) []url.URL {
usernameMatches := usernamePattern.FindAllStringSubmatch(dataStr, -1)
passwordMatches := passwordPattern.FindAllStringSubmatch(dataStr, -1)
hostnameMatches := hostnamePattern.FindAllStringSubmatch(dataStr, -1)
portMatches := portPattern.FindAllStringSubmatch(dataStr, -1)
var pgURLs []url.URL
hosts := findHosts(verify, hostnameMatches, portMatches)
for _, username := range dedupMatches(usernameMatches) {
for _, password := range dedupMatches(passwordMatches) {
for _, host := range hosts {
hostname, port := strings.Split(host, ":")[0], strings.Split(host, ":")[1]
if combinedLength := len(username) + len(password) + len(hostname); combinedLength > 255 {
continue
}
postgresURL := url.URL{
Scheme: "postgresql",
User: url.UserPassword(username, password),
Host: fmt.Sprintf("%s:%s", hostname, port),
}
pgURLs = append(pgURLs, postgresURL)
}
}
}
return pgURLs
}
// if verification is turned on, and we can confirm that postgres is running on at least one host,
// return only hosts where it's running. otherwise return all hosts.
func findHosts(verify bool, hostnameMatches, portMatches [][]string) []string {
hostnames := dedupMatches(hostnameMatches)
ports := dedupMatches(portMatches)
var hosts []string
if len(hostnames) < 1 {
hostnames = append(hostnames, defaultHost)
}
if len(ports) < 1 {
ports = append(ports, defaultPort)
}
for _, hostname := range hostnames {
for _, port := range ports {
hosts = append(hosts, fmt.Sprintf("%s:%s", hostname, port))
}
}
if verify {
var verifiedHosts []string
for _, host := range hosts {
parts := strings.Split(host, ":")
hostname, port := parts[0], parts[1]
if postgresRunning(hostname, port) {
verifiedHosts = append(verifiedHosts, host)
}
}
if len(verifiedHosts) > 0 {
return verifiedHosts
}
}
return hosts
}
// deduplicate matches in order to reduce the number of verification requests
func dedupMatches(matches [][]string) []string {
setOfMatches := make(map[string]struct{})
for _, match := range matches {
if len(match) > 1 {
setOfMatches[match[1]] = struct{}{}
}
}
var results []string
for match := range setOfMatches {
results = append(results, match)
}
return results
}
func verifyPostgres(pgURL *url.URL, timeoutInSeconds int) (bool, error) {
if pgURL.User == nil {
return false, nil
}
username := pgURL.User.Username()
password, _ := pgURL.User.Password()
hostname, port := pgURL.Hostname(), pgURL.Port()
if hostname == "" {
hostname = defaultHost
}
if port == "" {
port = defaultPort
}
sslmode := determineSSLMode(pgURL)
connStr := fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=%s", username, password, hostname, port, sslmode)
if timeoutInSeconds > 0 {
connStr = fmt.Sprintf("%s connect_timeout=%d", connStr, timeoutInSeconds)
}
db, err := sql.Open("postgres", connStr)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
// inactive host
return false, nil
}
return false, err
}
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err = db.PingContext(ctx)
if err == nil {
return true, nil
} else if strings.Contains(err.Error(), "password authentication failed") || // incorrect username or password
strings.Contains(err.Error(), "connection refused") { // inactive host
return false, nil
}
// if ssl is not enabled, manually fall-back to sslmode=disable
if strings.Contains(err.Error(), "SSL is not enabled on the server") {
pgURL.RawQuery = fmt.Sprintf("sslmode=%s", "disable")
return verifyPostgres(pgURL, timeoutInSeconds)
}
return false, err
}
func determineSSLMode(pgURL *url.URL) string {
// default ssl mode is "prefer" per https://www.postgresql.org/docs/current/libpq-ssl.html
// but is currently not implemented in the driver per https://github.com/lib/pq/issues/1006
// default for the driver is "require". ideally we would use "allow" but that is also not supported by the driver.
sslmode := "require"
if sslQuery, ok := pgURL.Query()["sslmode"]; ok && len(sslQuery) > 0 {
sslmode = sslQuery[0]
}
return sslmode
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Postgres
}
+340
View File
@@ -0,0 +1,340 @@
//go:build detectors
// +build detectors
package postgres
import (
"bytes"
"context"
"errors"
"fmt"
"os/exec"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
var postgresDockerHash string
const (
postgresUser = "postgres"
postgresPass = "23201dabb56ca236f3dc6736c0f9afad"
postgresHost = "localhost"
postgresPort = "5433"
inactiveUser = "inactive"
inactivePass = "inactive"
inactivePort = "61000"
inactiveHost = "192.0.2.0"
)
func TestPostgres_FromChunk(t *testing.T) {
startPostgres()
defer stopPostgres()
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
}{
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "found with seperated credentials, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(`
POSTGRES_USER=%s
POSTGRES_PASSWORD=%s
POSTGRES_ADDRESS=%s
POSTGRES_PORT=%s
`, postgresUser, postgresPass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: true,
},
},
wantErr: false,
},
{
name: "found with single line credentials, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, postgresPass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: true,
},
},
wantErr: false,
},
{
name: "found with json credentials, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
`DB_CONFIG={"user": "%s", "password": "%s", "host": "%s", "port": "%s", "database": "postgres"}`, postgresUser, postgresPass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: true,
},
},
wantErr: false,
},
{
name: "found with seperated credentials, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(`
POSTGRES_USER=%s
POSTGRES_PASSWORD=%s
POSTGRES_ADDRESS=%s
POSTGRES_PORT=%s
`, postgresUser, inactivePass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
},
},
wantErr: false,
},
{
name: "found with seperated credentials - no port, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(`
POSTGRES_USER=%s
POSTGRES_PASSWORD=%s
POSTGRES_ADDRESS=%s
`, postgresUser, inactivePass, postgresHost)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
},
},
wantErr: false,
},
{
name: "found with single line credentials, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, inactivePass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
},
},
wantErr: false,
},
{
name: "found with json credentials, unverified - inactive password",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
`DB_CONFIG={"user": "%s", "password": "%s", "host": "%s", "port": "%s", "database": "postgres"}`, postgresUser, inactivePass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
},
},
wantErr: false,
},
{
name: "found with json credentials, unverified - inactive user",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
`DB_CONFIG={"user": "%s", "password": "%s", "host": "%s", "port": "%s", "database": "postgres"}`, inactiveUser, postgresPass, postgresHost, postgresPort)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
},
},
wantErr: false,
},
{
name: "found, unverified due to error - inactive port",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, postgresPass, postgresHost, inactivePort)),
verify: true,
},
want: func() []detectors.Result {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
}
return []detectors.Result{r}
}(),
wantErr: false,
},
// This test seems take a long time to run (70s+) even with the timeout set to 1s. It's not clear why.
{
name: "found, unverified due to error - inactive host",
s: Scanner{},
args: func() args {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return args{
ctx: ctx,
data: []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, postgresPass, inactiveHost, postgresPort)),
verify: true,
}
}(),
want: func() []detectors.Result {
r := detectors.Result{
DetectorType: detectorspb.DetectorType_Postgres,
Verified: false,
}
r.SetVerificationError(errors.New("i/o timeout"))
return []detectors.Result{r}
}(),
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("postgres.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
gotErr := ""
if got[i].VerificationError() != nil {
gotErr = got[i].VerificationError().Error()
}
wantErr := ""
if tt.want[i].VerificationError() != nil {
wantErr = tt.want[i].VerificationError().Error()
}
if gotErr != wantErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.want[i].VerificationError(), got[i].VerificationError())
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Postgres.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func dockerLogLine(hash string, needle string) chan struct{} {
ch := make(chan struct{}, 1)
go func() {
for {
out, err := exec.Command("docker", "logs", hash).CombinedOutput()
if err != nil {
panic(err)
}
if strings.Contains(string(out), needle) {
ch <- struct{}{}
return
}
time.Sleep(1 * time.Second)
}
}()
return ch
}
func startPostgres() error {
cmd := exec.Command(
"docker", "run", "--rm", "-p", postgresPort+":"+defaultPort,
"-e", "POSTGRES_PASSWORD="+postgresPass,
"-e", "POSTGRES_USER="+postgresUser,
"-d", "postgres",
)
fmt.Println(cmd.String())
out, err := cmd.Output()
if err != nil {
return err
}
postgresDockerHash = string(bytes.TrimSpace(out))
select {
case <-dockerLogLine(postgresDockerHash, "PostgreSQL init process complete; ready for start up."):
return nil
case <-time.After(30 * time.Second):
stopPostgres()
return errors.New("timeout waiting for postgres database to be ready")
}
}
func stopPostgres() {
exec.Command("docker", "kill", postgresDockerHash).Run()
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+59 -25
View File
@@ -13,16 +13,19 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct{}
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"signable"}) + `\b([a-zA-Z-0-9]{32})\b`)
tokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{".{0,2}signable"}) + `\b([a-zA-Z-0-9]{32})\b`)
keywordPat = regexp.MustCompile(`(?i)([a-z]{2})signable`)
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -35,41 +38,34 @@ func (s Scanner) Keywords() []string {
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
matches := tokenPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
if isCommonFalsePositive(match[0]) {
continue
}
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Signable,
Raw: []byte(resMatch),
}
if verify {
data := fmt.Sprintf("%s:", resMatch)
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.signable.co.uk/v1/templates?offset=0&limit=5", nil)
if err != nil {
continue
if s.client == nil {
s.client = defaultClient
}
isVerified, verificationErr := verifyResult(ctx, s.client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
}
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
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
}
}
}
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
@@ -78,6 +74,44 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}
// Eliminate the most common false positive.
func isCommonFalsePositive(line string) bool {
// TODO: Skip lock files altogether. (https://github.com/trufflesecurity/trufflehog/issues/1517)
if strings.Contains(line, "helper-explode-assignable-expression") {
return true
}
// Eliminate false positives from `assignable` and `designable`.
for _, m := range keywordPat.FindAllStringSubmatch(line, -1) {
if strings.EqualFold(m[1], "as") || strings.EqualFold(m[1], "de") {
return true
}
}
return false
}
func verifyResult(ctx context.Context, client *http.Client, token string) (bool, error) {
data := fmt.Sprintf("%s:", token)
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.signable.co.uk/v1/templates?offset=0&limit=5", nil)
if err != nil {
return false, err
}
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc))
res, err := client.Do(req)
if err != nil {
return false, err
}
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
return true, nil
}
return false, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Signable
}
+150
View File
@@ -10,12 +10,162 @@ import (
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestSignable_Pattern(t *testing.T) {
tests := []struct {
name string
data string
shouldMatch bool
match string
}{
// True positives
{
name: "valid",
data: `const signableToken = '40a1cd917bff1288f699a94a75b37a1a'`,
shouldMatch: true,
match: "40a1cd917bff1288f699a94a75b37a1a",
},
// False positives
{
name: `invalid_assignable_yarn`,
data: `" babel-helper-explode-assignable-expression@^6.24.1:
version "6.24.1"
resolved "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
dependencies:
babel-runtime "^6.22.0"
babel-traverse "^6.24.1"
babel-types "^6.24.1"`,
shouldMatch: false,
},
{
name: `invalid_assignable_yarn`,
data: `"@babel/helper-explode-assignable-expression@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a"
integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==
dependencies:
"@babel/types" "^7.16.7"`,
shouldMatch: false,
},
// https://github.com/tbenst/purescript-nix-example/blob/558c8d6cb605742218cfa14a3fa93c062324b885/yarn.nix
{
name: `invalid_assignable_nix`,
data: ` {
name = "_babel_helper_explode_assignable_expression___helper_explode_assignable_expression_7.8.3.tgz";
path = fetchurl {
name = "_babel_helper_explode_assignable_expression___helper_explode_assignable_expression_7.8.3.tgz";
url = "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz";
sha1 = "a728dc5b4e89e30fc2dfc7d04fa28a930653f982";
};
}`,
shouldMatch: false,
},
{
name: `invalid_assignable`,
data: `<tr><td colspan="2"><br><h2>Public Member Functions</h2></td></tr>
<tr><td class="memItemLeft" nowrap align="right" valign="top"><a class="anchor" name="b0a0dbf6ca9028bbbb2240cad5882537"></a><!-- doxytag: member="boost::gil::Assignable::constraints" ref="b0a0dbf6ca9028bbbb2240cad5882537" args="()" -->
void&nbsp;</td><td class="memItemRight" valign="bottom"><b>constraints</b> ()</td></tr>
`,
shouldMatch: false,
},
{
name: `invalid_assignable`,
data: `File: enumIsAssignableToBuiltInEnum.kt - 6396cf8549625bfce8b8ca2511d7f347
NL("\n")
packageHeader`,
shouldMatch: false,
},
{
name: `invalid_assignable_php`,
data: `'./include/SugarObjects/forms/PersonFormBase.php' => '2c1846ef127d60a40ecbab2c0b312ff5',
'./include/SugarObjects/implements/assignable/language/en_us.lang.php' => '90f14b03e22e1eed2a1b93e10b975ef5',
'./include/SugarObjects/implements/assignable/vardefs.php' => '358e0c47f753c5577fbdc0de08553c02',
'./include/SugarObjects/implements/security_groups/language/en_us.lang.php' => 'ac1fd4817cb4662e3bdf973836558bdb',`,
shouldMatch: false,
},
// https://github.com/past-due/warzone2100/blob/3e5637a7ed3d67ab92e439b94bf93f89f7bbea51/ChangeLog#L318
{
name: `invalid_designable`,
data: ` * Fix: Prevent map selection button list from going off the form (commit:aea66eb1aa557c73d97b8019e5e66fccbb79f66e, #1347)
* Fix: Fix odd EMP mortar pathway; Add EMP mortar to designable weapons (commit:bcf93b7fe640c09e8b1239fabfd901fde9760259, #1535)`,
shouldMatch: false,
},
// https://github.com/exis-io/Exis/blob/5383174f7b52112a97aadd09e6b9ea837c2fa07b/CardsAgainstHumanityDemo/swiftCardsAgainst/Pods/Pods.xcodeproj/project.pbxproj
{
name: `invalid_designable`,
data: ` 570767CBD99941F484DED46232044DC3 /* DesignableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93111B182BD71051B9ED0B14A9EF6EB6 /* DesignableView.swift */; };
57140D31D50A2FDE1E26729DDE7CB762 /* M13ProgressHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = ECF777CB8B4C090E8D271E62729F7DD3 /* M13ProgressHUD.h */; settings = {ATTRIBUTES = (Public, ); }; };`,
shouldMatch: false,
},
{
name: `invalid_designable`,
data: `{"nick":"hamster88","message":"this is my gist > https://gist.github.com/thedesignable/a05f628c649a81aae757945c352a8392","date":"2016-06-19T11:05:13.776Z","type":"message"}`,
shouldMatch: false,
},
{
name: `invalid_designable`,
data: `返回到故事板文件,选择视图(我将假设从现在起视图被选中)并打开 Identity Inspector。你会注意到一个*可设计的*状态指示器已经出现在自定义类部分。
![Designables status](img/84bc9afc942815899347a31a425af7c6.png)`,
shouldMatch: false,
},
{
name: `invalid_designable`,
data: `&lt;h3 id=&#34;ibdesignable-x-paintcode:0b699a3cd6d609650a3fca90a5cd32cc&#34;&gt;IBDesignable x PaintCode&lt;/h3&gt;`,
shouldMatch: false,
},
{
name: `invalid_designable`,
data: ` </designables>
<resources>
<image name="a932cb605eb09bff88b88fdf1c3ef8aa" width="736" height="895"/>
<image name="plus" catalog="system" width="128" height="113"/>`,
shouldMatch: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := Scanner{}
results, err := s.FromData(context.Background(), false, []byte(test.data))
if err != nil {
t.Errorf("Signable.FromData() error = %v", err)
return
}
if test.shouldMatch {
if len(results) == 0 {
t.Errorf("%s: did not receive a match for '%v' when one was expected", test.name, test.data)
return
}
expected := test.data
if test.match != "" {
expected = test.match
}
result := results[0]
resultData := string(result.Raw)
if resultData != expected {
t.Errorf("%s: did not receive expected match.\n\texpected: '%s'\n\t actual: '%s'", test.name, expected, resultData)
return
}
} else {
if len(results) > 0 {
t.Errorf("%s: received a match for '%v' when one wasn't wanted", test.name, test.data)
return
}
}
})
}
}
func TestSignable_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
+5
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"regexp"
"strings"
"time"
"unicode"
_ "github.com/snowflakedb/gosnowflake"
@@ -116,6 +117,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
ctx = context.Background()
}
// Disable pool + retries to prevent flooding the server with failed login attemps.
db.SetConnMaxLifetime(time.Second)
db.SetMaxOpenConns(1)
err = db.PingContext(ctx)
if err != nil {
if strings.Contains(err.Error(), "Incorrect username or password was specified") {
+4 -1
View File
@@ -22,7 +22,7 @@ var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(sgp_[a-f0-9]{40})\b`)
keyPat = regexp.MustCompile(`\b(sgp_(?:[a-fA-F0-9]{16}|local)_[a-fA-F0-9]{40}|sgp_[a-fA-F0-9]{40}|[a-fA-F0-9]{40})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -47,6 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Sourcegraph,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/sourcegraph/",
}
if verify {
client := s.client
+14 -4
View File
@@ -25,15 +25,24 @@ func TestSourcegraph_FromChunk(t *testing.T) {
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("SOURCEGRAPH")
inactiveSecret := testSecrets.MustGetField("SOURCEGRAPH_INACTIVE")
secretV1 := testSecrets.MustGetField("SOURCEGRAPH_V1")
secretV2 := testSecrets.MustGetField("SOURCEGRAPH_V2")
secretV3 := testSecrets.MustGetField("SOURCEGRAPH_V3")
inactiveSecretV1 := testSecrets.MustGetField("SOURCEGRAPH_INACTIVE_V1")
inactiveSecretV2 := testSecrets.MustGetField("SOURCEGRAPH_INACTIVE_V2")
inactiveSecretV3 := testSecrets.MustGetField("SOURCEGRAPH_INACTIVE_V3")
secrets := []string{secretV1, secretV2, secretV3, inactiveSecretV1, inactiveSecretV2, inactiveSecretV3}
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
for _, secret := range secrets {
tests = append(tests, []struct {
name string
s Scanner
args args
@@ -122,6 +131,7 @@ func TestSourcegraph_FromChunk(t *testing.T) {
wantVerificationErr: true,
},
}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
@@ -137,7 +147,7 @@ func TestSourcegraph_FromChunk(t *testing.T) {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError", "ExtraData")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Sourcegraph.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
+1 -1
View File
@@ -18,7 +18,7 @@ var _ detectors.Detector = (*Scanner)(nil)
var (
//doesn't include test keys with "sk_test"
secretKey = regexp.MustCompile(`[rs]k_live_[a-zA-Z0-9]{20,30}`)
secretKey = regexp.MustCompile(`[rs]k_live_[a-zA-Z0-9]{20,247}`)
)
// Keywords are used for efficiently pre-filtering chunks.
+2
View File
@@ -1496,6 +1496,8 @@ func DefaultDetectors() []detectors.Detector {
speechtextai.Scanner{},
databox.Scanner{},
postbacks.Scanner{},
// Too noisy, needs attention
// postgres.Scanner{},
collect2.Scanner{},
uclassify.Scanner{},
holistic.Scanner{},
+17
View File
@@ -48,3 +48,20 @@ func TestDefaultDetectorTypesImplementing(t *testing.T) {
)
}
}
func TestDefaultVersionerDetectorsHaveNonZeroVersions(t *testing.T) {
// Loop through all our default detectors and find the ones that
// implement Versioner. Of those, check each version is not zero.
// This is required due to an implementation detail of filtering detectors.
// See: https://github.com/trufflesecurity/trufflehog/blob/v3.63.7/main.go#L624-L638
for _, detector := range DefaultDetectors() {
v, ok := detector.(detectors.Versioner)
if !ok || v.Version() != 0 {
continue
}
t.Errorf(
"detector %q implements Versioner that returns a zero version",
detectorspb.DetectorType_name[int32(detector.Type())],
)
}
}
+5
View File
@@ -11,6 +11,7 @@ import (
lru "github.com/hashicorp/golang-lru"
"google.golang.org/protobuf/proto"
"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
@@ -425,6 +426,10 @@ func (e *Engine) Finish(ctx context.Context) error {
close(e.results) // Detector workers are done, close the results channel and call it a day.
e.WgNotifier.Wait() // Wait for the notifier workers to finish notifying results.
if err := cleantemp.CleanTempArtifacts(ctx); err != nil {
ctx.Logger().Error(err, "error cleaning temp artifacts")
}
e.metrics.ScanDuration = time.Since(e.metrics.scanStartTime)
return err
+3 -2
View File
@@ -15,7 +15,9 @@ import (
// ScanFileSystem scans a given file system.
func (e *Engine) ScanFileSystem(ctx context.Context, c sources.FilesystemConfig) error {
connection := &sourcespb.Filesystem{
Paths: c.Paths,
Paths: c.Paths,
IncludePathsFile: c.IncludePathsFile,
ExcludePathsFile: c.ExcludePathsFile,
}
var conn anypb.Any
err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
@@ -28,7 +30,6 @@ func (e *Engine) ScanFileSystem(ctx context.Context, c sources.FilesystemConfig)
sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, filesystem.SourceType)
fileSystemSource := &filesystem.Source{}
fileSystemSource.WithFilter(c.Filter)
if err := fileSystemSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
return err
}
+10 -6
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"fmt"
"github.com/go-logr/logr"
"io"
"os"
"os/exec"
@@ -92,6 +93,7 @@ func (state ParseState) String() string {
"BinaryFileLine",
"HunkLineNumberLine",
"HunkContentLine",
"ParseFailure",
}[state]
}
@@ -313,7 +315,7 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
case isMessageLine(isStaged, latestState, line):
latestState = MessageLine
currentCommit.Message.Write(line[4:])
currentCommit.Message.Write(line[4:]) // Messages are indented with 4 spaces.
case isMessageEndLine(isStaged, latestState, line):
latestState = MessageEndLine
// NoOp
@@ -425,13 +427,14 @@ func (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, commitChan ch
// Here be dragons...
// Build an informative error message.
var err error
err := fmt.Errorf(`invalid line "%s" after state "%s"`, line, latestState)
var logger logr.Logger
if currentCommit != nil && currentCommit.Hash != "" {
err = fmt.Errorf(`failed to parse line "%s" after state "%s" (commit=%s)`, line, latestState, currentCommit.Hash)
logger = ctx.Logger().WithValues("commit", currentCommit.Hash)
} else {
err = fmt.Errorf(`failed to parse line "%s" after state "%s"`, line, latestState)
logger = ctx.Logger()
}
ctx.Logger().V(2).Error(err, "Recovering at the latest commit or diff...\n")
logger.Error(err, "failed to parse Git input. Recovering at the latest commit or diff...")
latestState = ParseFailure
}
@@ -612,8 +615,9 @@ func pathFromBinaryLine(line []byte) string {
}
// --- a/internal/addrs/move_endpoint_module.go
// --- /dev/null
func isFromFileLine(isStaged bool, latestState ParseState, line []byte) bool {
if latestState != IndexLine {
if !(latestState == IndexLine || latestState == ModeLine) {
return false
}
if len(line) >= 6 && bytes.Equal(line[:4], []byte("--- ")) {
+73 -1
View File
@@ -323,6 +323,13 @@ func TestLineChecks(t *testing.T) {
IndexLine,
[]byte("--- /dev/null"),
},
// New file (https://github.com/trufflesecurity/trufflehog/issues/2109)
// diff --git a/libs/Unfit-1.0 b/libs/Unfit-1.0
// new file mode 160000
{
ModeLine,
[]byte("--- /dev/null"),
},
// Uncommon but valid prefixes. Will these ever show up?
// https://stackoverflow.com/a/2530012
// https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffmnemonicPrefix
@@ -1148,7 +1155,45 @@ func TestMaxCommitSize(t *testing.T) {
}
const commitLog = `commit 4727ffb7ad6dc5130bf4b4dd166e00705abdd018 (HEAD -> master)
const commitLog = `commit fd6e99e7a80199b76a694603be57c5ade1de18e7
Author: Jaliborc <[email protected]>
Date: Mon Apr 25 16:28:06 2011 +0100
Added Unusable coloring
diff --git a/components/item.lua b/components/item.lua
index fc74534..f8d7d50 100755
--- a/components/item.lua
+++ b/components/item.lua
@@ -9,6 +9,7 @@ ItemSlot:Hide()
Bagnon.ItemSlot = ItemSlot
local ItemSearch = LibStub('LibItemSearch-1.0')
+local Unfit = LibStub('Unfit-1.0')
local function hasBlizzQuestHighlight()
return GetContainerItemQuestInfo and true or false
diff --git a/embeds.xml b/embeds.xml
index d3f4e7c..0c2df69 100755
--- a/embeds.xml
+++ b/embeds.xml
@@ -6,6 +6,7 @@
<Include file="libs\AceConsole-3.0\AceConsole-3.0.xml"/>
<Include file="libs\AceLocale-3.0\AceLocale-3.0.xml"/>
+ <Script file="libs\Unfit-1.0\Unfit-1.0.lua"/>
<Script file="libs\LibDataBroker-1.1.lua"/>
<Script file="libs\LibItemSearch-1.0\LibItemSearch-1.0.lua"/>
</Ui>
\ No newline at end of file
diff --git a/libs/Unfit-1.0 b/libs/Unfit-1.0
new file mode 160000
--- /dev/null
+++ b/libs/Unfit-1.0
@@ -0,0 +1 @@
+Subproject commit 0000000000000000000000000000000000000000
commit 4727ffb7ad6dc5130bf4b4dd166e00705abdd018 (HEAD -> master)
Author: John Smith <[email protected]>
Date: Tue Jul 11 22:26:11 2023 -0400
@@ -1539,6 +1584,33 @@ index 0000000..5af88a8
// This throws a nasty panic if it's a top-level var.
func expectedCommits() []Commit {
return []Commit{
// a
{
Hash: "fd6e99e7a80199b76a694603be57c5ade1de18e7",
Author: "Jaliborc <[email protected]>",
Date: newTime("Mon Apr 25 16:28:06 2011 +0100"),
Message: newStringBuilderValue("Added Unusable coloring\n"),
Diffs: []Diff{
{
PathB: "components/item.lua",
LineStart: 9,
Content: *bytes.NewBuffer([]byte("\n\nlocal Unfit = LibStub('Unfit-1.0')\n\n\n")),
IsBinary: false,
},
{
PathB: "embeds.xml",
LineStart: 6,
Content: *bytes.NewBuffer([]byte("\n\n <Script file=\"libs\\Unfit-1.0\\Unfit-1.0.lua\"/>\n\n\n\n")),
IsBinary: false,
},
{
PathB: "libs/Unfit-1.0",
LineStart: 1,
Content: *bytes.NewBuffer([]byte("Subproject commit 0000000000000000000000000000000000000000\n")),
IsBinary: false,
},
},
},
// Empty commit and message. Lord help us.
{
Hash: "4727ffb7ad6dc5130bf4b4dd166e00705abdd018",
+2 -2
View File
@@ -120,7 +120,7 @@ func NormalizeOrgRepoURL(provider provider, repoURL string) (string, error) {
func GenerateLink(repo, commit, file string, line int64) string {
// Some paths contain '%' which breaks |url.Parse| if not encoded.
// https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding
file = strings.Replace(file, "%", "%25", -1)
file = strings.ReplaceAll(file, "%", "%25")
switch determineProvider(repo) {
case providerBitbucket:
@@ -140,7 +140,7 @@ func GenerateLink(repo, commit, file string, line int64) string {
default:
var baseLink string
//Gist links are formatted differently
// Gist links are formatted differently
if strings.HasPrefix(repo, "https://gist.github.com") {
baseLink = repo[:len(repo)-4] + "/"
if commit != "" {
+18 -6
View File
@@ -46,6 +46,7 @@ type Archive struct {
size int
currentDepth int
skipBinaries bool
skipArchives bool
}
// New creates a new Archive handler with the provided options.
@@ -72,6 +73,10 @@ func SetArchiveMaxTimeout(timeout time.Duration) {
// FromFile extracts the files from an archive.
func (a *Archive) FromFile(originalCtx logContext.Context, data io.Reader) chan []byte {
if a.skipArchives {
return nil
}
archiveChan := make(chan []byte, defaultBufferSize)
go func() {
ctx, cancel := logContext.WithTimeout(originalCtx, maxTimeout)
@@ -91,6 +96,10 @@ func (a *Archive) FromFile(originalCtx logContext.Context, data io.Reader) chan
// openArchive takes a reader and extracts the contents up to the maximum depth.
func (a *Archive) openArchive(ctx logContext.Context, depth int, reader io.Reader, archiveChan chan []byte) error {
if common.IsDone(ctx) {
return ctx.Err()
}
if depth >= maxDepth {
return fmt.Errorf(errMaxArchiveDepthReached)
}
@@ -111,6 +120,9 @@ func (a *Archive) openArchive(ctx logContext.Context, depth int, reader io.Reade
if err != nil {
return err
}
defer compReader.Close()
return a.openArchive(ctx, depth+1, compReader, archiveChan)
case archiver.Extractor:
return archive.Extract(logContext.WithValue(ctx, depthKey, depth+1), arReader, nil, a.extractorHandler(archiveChan))
@@ -180,6 +192,11 @@ func (a *Archive) extractorHandler(archiveChan chan []byte) func(context.Context
return func(ctx context.Context, f archiver.File) error {
lCtx := logContext.AddLogger(ctx)
lCtx.Logger().V(5).Info("Handling extracted file.", "filename", f.Name())
if common.IsDone(ctx) {
return ctx.Err()
}
depth := 0
if ctxDepth, ok := ctx.Value(depthKey).(int); ok {
depth = ctxDepth
@@ -201,12 +218,7 @@ func (a *Archive) extractorHandler(archiveChan chan []byte) func(context.Context
return nil
}
fileBytes, err := a.ReadToMax(lCtx, fReader)
if err != nil {
return err
}
return a.openArchive(lCtx, depth, bytes.NewReader(fileBytes), archiveChan)
return a.openArchive(lCtx, depth, fReader, archiveChan)
}
}
+101 -35
View File
@@ -76,36 +76,38 @@ func TestArchiveHandler(t *testing.T) {
}
for name, testCase := range tests {
resp, err := http.Get(testCase.archiveURL)
if err != nil || resp.StatusCode != http.StatusOK {
t.Error(err)
}
defer resp.Body.Close()
archive := Archive{}
archive.New()
newReader, err := diskbufferreader.New(resp.Body)
if err != nil {
t.Errorf("error creating reusable reader: %s", err)
}
archiveChan := archive.FromFile(logContext.Background(), newReader)
count := 0
re := regexp.MustCompile(testCase.matchString)
matched := false
for chunk := range archiveChan {
count++
if re.Match(chunk) {
matched = true
t.Run(name, func(t *testing.T) {
resp, err := http.Get(testCase.archiveURL)
if err != nil || resp.StatusCode != http.StatusOK {
t.Error(err)
}
}
if !matched && len(testCase.matchString) > 0 {
t.Errorf("%s: Expected string not found in archive.", name)
}
if count != testCase.expectedChunks {
t.Errorf("%s: Unexpected number of chunks. Got %d, expected: %d", name, count, testCase.expectedChunks)
}
defer resp.Body.Close()
archive := Archive{}
archive.New()
newReader, err := diskbufferreader.New(resp.Body)
if err != nil {
t.Errorf("error creating reusable reader: %s", err)
}
archiveChan := archive.FromFile(logContext.Background(), newReader)
count := 0
re := regexp.MustCompile(testCase.matchString)
matched := false
for chunk := range archiveChan {
count++
if re.Match(chunk) {
matched = true
}
}
if !matched && len(testCase.matchString) > 0 {
t.Errorf("%s: Expected string not found in archive.", name)
}
if count != testCase.expectedChunks {
t.Errorf("%s: Unexpected number of chunks. Got %d, expected: %d", name, count, testCase.expectedChunks)
}
})
}
}
@@ -115,7 +117,9 @@ func TestHandleFile(t *testing.T) {
// Context cancels the operation.
canceledCtx, cancel := logContext.WithCancel(logContext.Background())
cancel()
assert.False(t, HandleFile(canceledCtx, strings.NewReader("file"), &sources.Chunk{}, reporter))
reader, err := diskbufferreader.New(strings.NewReader("file"))
assert.NoError(t, err)
assert.False(t, HandleFile(canceledCtx, reader, &sources.Chunk{}, reporter))
// Only one chunk is sent on the channel.
// TODO: Embed a zip without making an HTTP request.
@@ -124,7 +128,7 @@ func TestHandleFile(t *testing.T) {
defer resp.Body.Close()
archive := Archive{}
archive.New()
reader, err := diskbufferreader.New(resp.Body)
reader, err = diskbufferreader.New(resp.Body)
assert.NoError(t, err)
assert.Equal(t, 0, len(reporter.Ch))
@@ -132,6 +136,34 @@ func TestHandleFile(t *testing.T) {
assert.Equal(t, 1, len(reporter.Ch))
}
func BenchmarkHandleFile(b *testing.B) {
file, err := os.Open("testdata/test.tgz")
assert.Nil(b, err)
defer file.Close()
archive := Archive{}
archive.New()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sourceChan := make(chan *sources.Chunk, 1)
reader, err := diskbufferreader.New(file)
assert.NoError(b, err)
b.StartTimer()
go func() {
defer close(sourceChan)
HandleFile(logContext.Background(), reader, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})
}()
for range sourceChan {
}
b.StopTimer()
}
}
func TestHandleFileSkipBinaries(t *testing.T) {
filename := createBinaryArchive(t)
defer os.Remove(filename)
@@ -139,13 +171,16 @@ func TestHandleFileSkipBinaries(t *testing.T) {
file, err := os.Open(filename)
assert.NoError(t, err)
reader, err := diskbufferreader.New(file)
assert.NoError(t, err)
ctx, cancel := logContext.WithTimeout(logContext.Background(), 5*time.Second)
defer cancel()
sourceChan := make(chan *sources.Chunk, 1)
go func() {
defer close(sourceChan)
HandleFile(ctx, file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan}, WithSkipBinaries(true))
HandleFile(ctx, reader, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan}, WithSkipBinaries(true))
}()
count := 0
@@ -278,17 +313,45 @@ func TestExtractDebContent(t *testing.T) {
assert.Equal(t, expectedLength, len(string(content)))
}
func TestExtractTarContent(t *testing.T) {
func TestSkipArchive(t *testing.T) {
file, err := os.Open("testdata/test.tgz")
assert.Nil(t, err)
defer file.Close()
reader, err := diskbufferreader.New(file)
assert.NoError(t, err)
ctx := logContext.Background()
chunkCh := make(chan *sources.Chunk)
go func() {
defer close(chunkCh)
ok := HandleFile(ctx, file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})
ok := HandleFile(ctx, reader, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh}, WithSkipArchives(true))
assert.False(t, ok)
}()
wantCount := 0
count := 0
for range chunkCh {
count++
}
assert.Equal(t, wantCount, count)
}
func TestExtractTarContent(t *testing.T) {
file, err := os.Open("testdata/test.tgz")
assert.Nil(t, err)
defer file.Close()
reader, err := diskbufferreader.New(file)
assert.NoError(t, err)
ctx := logContext.Background()
chunkCh := make(chan *sources.Chunk)
go func() {
defer close(chunkCh)
ok := HandleFile(ctx, reader, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})
assert.True(t, ok)
}()
@@ -335,13 +398,16 @@ func TestNestedDirArchive(t *testing.T) {
assert.Nil(t, err)
defer file.Close()
reader, err := diskbufferreader.New(file)
assert.NoError(t, err)
ctx, cancel := logContext.WithTimeout(logContext.Background(), 5*time.Second)
defer cancel()
sourceChan := make(chan *sources.Chunk, 1)
go func() {
defer close(sourceChan)
HandleFile(ctx, file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})
HandleFile(ctx, reader, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})
}()
count := 0
+15 -14
View File
@@ -36,6 +36,15 @@ func WithSkipBinaries(skip bool) Option {
}
}
// WithSkipArchives returns a Option that configures whether to skip archive files.
func WithSkipArchives(skip bool) Option {
return func(h Handler) {
if a, ok := h.(*Archive); ok {
a.skipArchives = skip
}
}
}
type Handler interface {
FromFile(logContext.Context, io.Reader) chan []byte
IsFiletype(logContext.Context, io.Reader) (io.Reader, bool)
@@ -48,20 +57,11 @@ type Handler interface {
// packages them in the provided chunk skeleton, and reports them to the chunk reporter.
// The function returns true if processing was successful and false otherwise.
// Context is used for cancellation, and the caller is responsible for canceling it if needed.
func HandleFile(ctx logContext.Context, file io.Reader, chunkSkel *sources.Chunk, reporter sources.ChunkReporter, opts ...Option) bool {
func HandleFile(ctx logContext.Context, reReader *diskbufferreader.DiskBufferReader, chunkSkel *sources.Chunk, reporter sources.ChunkReporter, opts ...Option) bool {
for _, h := range DefaultHandlers() {
h.New(opts...)
// The re-reader is used to reset the file reader after checking if the handler implements SpecializedHandler.
// This is necessary because the archive pkg doesn't correctly determine the file type when using
// an io.MultiReader, which is used by the SpecializedHandler.
reReader, err := diskbufferreader.New(file)
if err != nil {
ctx.Logger().Error(err, "error creating reusable reader")
return false
}
if success := processHandler(ctx, h, reReader, chunkSkel, reporter); success {
if handled := processHandler(ctx, h, reReader, chunkSkel, reporter); handled {
return true
}
}
@@ -70,9 +70,6 @@ func HandleFile(ctx logContext.Context, file io.Reader, chunkSkel *sources.Chunk
}
func processHandler(ctx logContext.Context, h Handler, reReader *diskbufferreader.DiskBufferReader, chunkSkel *sources.Chunk, reporter sources.ChunkReporter) bool {
defer reReader.Close()
defer reReader.Stop()
if specialHandler, ok := h.(SpecializedHandler); ok {
file, isSpecial, err := specialHandler.HandleSpecialized(ctx, reReader)
if isSpecial {
@@ -96,6 +93,10 @@ func processHandler(ctx logContext.Context, h Handler, reReader *diskbufferreade
}
func handleChunks(ctx logContext.Context, handlerChan chan []byte, chunkSkel *sources.Chunk, reporter sources.ChunkReporter) bool {
if handlerChan == nil {
return false
}
for {
select {
case data, open := <-handlerChan:
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16
View File
@@ -587,6 +587,8 @@ func (m *Bitbucket) validate(all bool) error {
// no validation rules for SkipBinaries
// no validation rules for SkipArchives
switch m.Credential.(type) {
case *Bitbucket_Token:
@@ -1482,6 +1484,10 @@ func (m *Filesystem) validate(all bool) error {
var errors []error
// no validation rules for IncludePathsFile
// no validation rules for ExcludePathsFile
if len(errors) > 0 {
return FilesystemMultiError(errors)
}
@@ -1806,6 +1812,8 @@ func (m *Git) validate(all bool) error {
// no validation rules for SkipBinaries
// no validation rules for SkipArchives
switch m.Credential.(type) {
case *Git_BasicAuth:
@@ -2015,6 +2023,8 @@ func (m *GitLab) validate(all bool) error {
// no validation rules for SkipBinaries
// no validation rules for SkipArchives
switch m.Credential.(type) {
case *GitLab_Token:
@@ -2210,6 +2220,8 @@ func (m *GitHub) validate(all bool) error {
// no validation rules for SkipBinaries
// no validation rules for SkipArchives
switch m.Credential.(type) {
case *GitHub_GithubApp:
@@ -3594,6 +3606,8 @@ func (m *Gerrit) validate(all bool) error {
// no validation rules for SkipBinaries
// no validation rules for SkipArchives
switch m.Credential.(type) {
case *Gerrit_BasicAuth:
@@ -4681,6 +4695,8 @@ func (m *AzureRepos) validate(all bool) error {
// no validation rules for SkipBinaries
// no validation rules for SkipArchives
switch m.Credential.(type) {
case *AzureRepos_Token:
+12 -5
View File
@@ -144,15 +144,22 @@ func readInChunks(ctx context.Context, reader io.Reader, config *chunkReaderConf
// If there is an error other than EOF, or if we have read some bytes, send the chunk.
// io.ReadFull will only return io.EOF when n == 0.
if isErrAndNotEOF(err) {
switch {
case isErrAndNotEOF(err):
ctx.Logger().Error(err, "error reading chunk")
chunkRes.err = err
chunkResultChan <- chunkRes
} else if n > 0 {
chunkResultChan <- chunkRes
case n > 0:
chunkRes.err = nil
default:
return
}
select {
case <-ctx.Done():
return
case chunkResultChan <- chunkRes:
}
// Return on any type of error.
if err != nil {
return
}
+35
View File
@@ -3,9 +3,12 @@ package sources
import (
"bytes"
"io"
"math/rand"
"runtime"
"strings"
"testing"
"testing/iotest"
"time"
"github.com/stretchr/testify/assert"
diskbufferreader "github.com/trufflesecurity/disk-buffer-reader"
@@ -217,3 +220,35 @@ func TestFlakyChunkReader(t *testing.T) {
assert.NoError(t, chunk.Error())
assert.Equal(t, a+b, string(chunk.Bytes()))
}
func TestReadInChunksWithCancellation(t *testing.T) {
largeData := strings.Repeat("large test data ", 1024*1024) // Large data string.
for i := 0; i < 10; i++ {
initialGoroutines := runtime.NumGoroutine()
for j := 0; j < 5; j++ { // Call readInChunks multiple times
ctx, cancel := context.WithCancel(context.Background())
reader := strings.NewReader(largeData)
chunkReader := NewChunkReader()
chunkChan := chunkReader(ctx, reader)
if rand.Intn(2) == 0 { // Randomly decide to cancel the context
cancel()
} else {
for range chunkChan {
}
}
}
// Allow for goroutine finalization.
time.Sleep(time.Millisecond * 100)
// Check for goroutine leaks.
if runtime.NumGoroutine() > initialGoroutines {
t.Error("Potential goroutine leak detected")
}
}
}
+6 -4
View File
@@ -71,11 +71,13 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
}
s.paths = append(conn.Paths, conn.Directories...)
return nil
}
func (s *Source) WithFilter(filter *common.Filter) {
filter, err := common.FilterFromFiles(conn.IncludePathsFile, conn.ExcludePathsFile)
if err != nil {
return fmt.Errorf("unable to create filter: %w", err)
}
s.filter = filter
return nil
}
// Chunks emits chunks of bytes over a channel.
+9 -2
View File
@@ -97,7 +97,7 @@ func newPersistableCache(increment int, cache cache.Cache, p *sources.Progress)
// Set overrides the cache Set method of the cache to enable the persistence
// of the cache contents the Progress of the source at given increments.
func (c *persistableCache) Set(key, val string) {
func (c *persistableCache) Set(key string, val any) {
c.Cache.Set(key, val)
if ok, contents := c.shouldPersist(); ok {
c.Progress.EncodedResumeInfo = contents
@@ -297,7 +297,14 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
func (s *Source) setupCache(ctx context.Context) *persistableCache {
var c cache.Cache
if s.Progress.EncodedResumeInfo != "" {
c = memory.NewWithData(ctx, strings.Split(s.Progress.EncodedResumeInfo, ","))
keys := strings.Split(s.Progress.EncodedResumeInfo, ",")
entries := make([]memory.CacheEntry, len(keys))
for i, val := range keys {
entries[i] = memory.CacheEntry{Key: val, Value: val}
}
c = memory.NewWithData(entries)
ctx.Logger().V(3).Info("Loaded cache", "num_entries", len(entries))
} else {
c = memory.New()
}
+39 -40
View File
@@ -5,11 +5,11 @@ import (
"bytes"
"errors"
"fmt"
"io"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync/atomic"
@@ -19,12 +19,13 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/google/go-github/v42/github"
diskbufferreader "github.com/trufflesecurity/disk-buffer-reader"
"golang.org/x/oauth2"
"golang.org/x/sync/semaphore"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
diskbufferreader "github.com/trufflesecurity/disk-buffer-reader"
"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
@@ -59,6 +60,7 @@ type Git struct {
metrics metrics
concurrency *semaphore.Weighted
skipBinaries bool
skipArchives bool
}
type metrics struct {
@@ -67,6 +69,7 @@ type metrics struct {
func NewGit(sourceType sourcespb.SourceType, jobID sources.JobID, sourceID sources.SourceID, sourceName string, verify bool, concurrency int,
sourceMetadataFunc func(file, email, commit, timestamp, repository string, line int64) *source_metadatapb.MetaData, skipBinaries bool,
skipArchives bool,
) *Git {
return &Git{
sourceType: sourceType,
@@ -77,6 +80,7 @@ func NewGit(sourceType sourcespb.SourceType, jobID sources.JobID, sourceID sourc
verify: verify,
concurrency: semaphore.NewWeighted(int64(concurrency)),
skipBinaries: skipBinaries,
skipArchives: skipArchives,
}
}
@@ -179,6 +183,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
}
},
conn.GetSkipBinaries(),
conn.GetSkipArchives(),
)
return nil
}
@@ -447,11 +452,18 @@ func CloneRepoUsingUnauthenticated(ctx context.Context, url string, args ...stri
}
// CloneRepoUsingSSH clones a repo using SSH.
func CloneRepoUsingSSH(ctx context.Context, gitUrl string, args ...string) (string, *git.Repository, error) {
func CloneRepoUsingSSH(ctx context.Context, gitURL string, args ...string) (string, *git.Repository, error) {
if isCodeCommitURL(gitURL) {
return CloneRepo(ctx, nil, gitURL, args...)
}
userInfo := url.User("git")
return CloneRepo(ctx, userInfo, gitUrl, args...)
return CloneRepo(ctx, userInfo, gitURL, args...)
}
var codeCommitRE = regexp.MustCompile(`ssh://git-codecommit\.[\w-]+\.amazonaws\.com`)
func isCodeCommitURL(gitURL string) bool { return codeCommitRE.MatchString(gitURL) }
func (s *Git) CommitsScanned() uint64 {
return atomic.LoadUint64(&s.metrics.commitsScanned)
}
@@ -459,7 +471,16 @@ func (s *Git) CommitsScanned() uint64 {
const gitDirName = ".git"
func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error {
commitChan, err := gitparse.NewParser().RepoPath(ctx, path, scanOptions.HeadHash, scanOptions.BaseHash == "", scanOptions.ExcludeGlobs, scanOptions.Bare)
// Get the remote URL for reporting (may be empty)
remoteURL := getSafeRemoteURL(repo, "origin")
var repoCtx context.Context
if remoteURL != "" {
repoCtx = context.WithValue(ctx, "repo", remoteURL)
} else {
repoCtx = context.WithValue(ctx, "repo", path)
}
commitChan, err := gitparse.NewParser().RepoPath(repoCtx, path, scanOptions.HeadHash, scanOptions.BaseHash == "", scanOptions.ExcludeGlobs, scanOptions.Bare)
if err != nil {
return err
}
@@ -467,14 +488,10 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string
return nil
}
// get the URL metadata for reporting (may be empty)
urlMetadata := getSafeRemoteURL(repo, "origin")
var depth int64
gitDir := filepath.Join(path, gitDirName)
logger := ctx.Logger().WithValues("repo", urlMetadata)
logger := repoCtx.Logger()
logger.V(1).Info("scanning repo", "base", scanOptions.BaseHash, "head", scanOptions.HeadHash)
for commit := range commitChan {
if len(scanOptions.BaseHash) > 0 {
@@ -507,7 +524,7 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string
// Handle binary files by reading the entire file rather than using the diff.
if diff.IsBinary {
commitHash := plumbing.NewHash(hash)
metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, 0)
metadata := s.sourceMetadataFunc(fileName, email, hash, when, remoteURL, 0)
chunkSkel := &sources.Chunk{
SourceName: s.sourceName,
SourceID: s.sourceID,
@@ -523,10 +540,10 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string
}
if diff.Content.Len() > sources.ChunkSize+sources.PeekSize {
s.gitChunk(ctx, diff, fileName, email, hash, when, urlMetadata, reporter)
s.gitChunk(ctx, diff, fileName, email, hash, when, remoteURL, reporter)
continue
}
metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, int64(diff.LineStart))
metadata := s.sourceMetadataFunc(fileName, email, hash, when, remoteURL, int64(diff.LineStart))
chunk := sources.Chunk{
SourceName: s.sourceName,
SourceID: s.sourceID,
@@ -1006,16 +1023,17 @@ func (s *Git) handleBinary(ctx context.Context, gitDir string, reporter sources.
return nil
}
var handlerOpts []handlers.Option
if s.skipBinaries {
handlerOpts = append(handlerOpts, handlers.WithSkipBinaries(true))
if common.IsBinary(path) {
fileCtx.Logger().V(5).Info("skipping binary file")
return nil
}
fileCtx.Logger().V(5).Info("skipping binary file", "path", path)
return nil
}
var handlerOpts []handlers.Option
if s.skipArchives {
handlerOpts = append(handlerOpts, handlers.WithSkipArchives(true))
}
const maxSize = 1 * 1024 * 1024 * 1024 // 1GB
cmd := exec.Command("git", "-C", gitDir, "cat-file", "blob", commitHash.String()+":"+path)
var stderr bytes.Buffer
@@ -1043,31 +1061,12 @@ func (s *Git) handleBinary(ctx context.Context, gitDir string, reporter sources.
}
}()
var fileContent bytes.Buffer
// Create a limited reader to ensure we don't read more than the max size.
lr := io.LimitReader(fileReader, int64(maxSize))
// Using io.CopyBuffer for performance advantages. Though buf is mandatory
// for the method, due to the internal implementation of io.CopyBuffer, when
// *bytes.Buffer implements io.WriterTo or io.ReaderFrom, the provided buf
// is simply ignored. Thus, we can pass nil for the buf parameter.
_, err = io.CopyBuffer(&fileContent, lr, nil)
if err != nil && !errors.Is(err, io.EOF) {
return err
}
if fileContent.Len() == maxSize {
fileCtx.Logger().V(2).Info("Max archive size reached.")
}
bufferName := cleantemp.MkFilename()
reader, err := diskbufferreader.New(&fileContent, diskbufferreader.WithBufferName(bufferName))
reader, err := diskbufferreader.New(fileReader, diskbufferreader.WithBufferName(bufferName))
if err != nil {
return err
}
defer reader.Close()
if handlers.HandleFile(fileCtx, reader, chunkSkel, reporter, handlerOpts...) {
+44 -40
View File
@@ -1,6 +1,7 @@
package github
import (
"errors"
"fmt"
"net/http"
"net/url"
@@ -15,7 +16,6 @@ import (
"time"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5"
"github.com/go-logr/logr"
"github.com/gobwas/glob"
@@ -217,7 +217,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, so
var conn sourcespb.GitHub
err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})
if err != nil {
return errors.WrapPrefix(err, "error unmarshalling connection", 0)
return fmt.Errorf("error unmarshalling connection: %w", err)
}
s.conn = &conn
@@ -277,6 +277,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, so
}
},
conn.GetSkipBinaries(),
conn.GetSkipArchives(),
)
return nil
@@ -322,7 +323,7 @@ func (s *Source) Validate(ctx context.Context) []error {
errs = append(errs, fmt.Errorf("error creating GitHub client: %+v", err))
}
default:
errs = append(errs, errors.Errorf("Invalid configuration given for source. Name: %s, Type: %s", s.name, s.Type()))
errs = append(errs, fmt.Errorf("Invalid configuration given for source. Name: %s, Type: %s", s.name, s.Type()))
}
// Run a simple query to check if the client is actually valid
@@ -356,6 +357,10 @@ func (s *Source) visibilityOf(ctx context.Context, repoURL string) (visibility s
s.mu.Unlock()
}()
logger := s.log.WithValues("repo", repoURL)
if _, unauthenticated := s.conn.GetCredential().(*sourcespb.GitHub_Unauthenticated); unauthenticated {
logger.V(3).Info("assuming unauthenticated scan has public visibility")
return source_metadatapb.Visibility_public
}
logger.V(2).Info("Checking public status")
u, err := url.Parse(repoURL)
if err != nil {
@@ -378,10 +383,6 @@ func (s *Source) visibilityOf(ctx context.Context, repoURL string) (visibility s
}
}
if err != nil || gist == nil {
if _, unauthenticated := s.conn.GetCredential().(*sourcespb.GitHub_Unauthenticated); unauthenticated {
logger.Info("Unauthenticated scans cannot determine if a repository is private.")
visibility = source_metadatapb.Visibility_private
}
logger.Error(err, "Could not get Github repository")
return
}
@@ -401,10 +402,6 @@ func (s *Source) visibilityOf(ctx context.Context, repoURL string) (visibility s
}
if err != nil || repo == nil {
logger.Error(err, "Could not get Github repository")
if _, unauthenticated := s.conn.GetCredential().(*sourcespb.GitHub_Unauthenticated); unauthenticated {
logger.Info("Unauthenticated scans cannot determine if a repository is private.")
visibility = source_metadatapb.Visibility_private
}
return
}
if *repo.Private {
@@ -418,11 +415,13 @@ func (s *Source) visibilityOf(ctx context.Context, repoURL string) (visibility s
return
}
const cloudEndpoint = "https://api.github.com"
// Chunks emits chunks of bytes over a channel.
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, targets ...sources.ChunkingTarget) error {
apiEndpoint := s.conn.Endpoint
if len(apiEndpoint) == 0 || endsWithGithub.MatchString(apiEndpoint) {
apiEndpoint = "https://api.github.com"
apiEndpoint = cloudEndpoint
}
// If targets are provided, we're only scanning the data in those targets.
@@ -468,10 +467,18 @@ func (s *Source) enumerate(ctx context.Context, apiEndpoint string) (*github.Cli
}
default:
// TODO: move this error to Init
return nil, errors.Errorf("Invalid configuration given for source. Name: %s, Type: %s", s.name, s.Type())
return nil, fmt.Errorf("Invalid configuration given for source. Name: %s, Type: %s", s.name, s.Type())
}
s.repos = s.filteredRepoCache.Values()
s.repos = make([]string, 0, s.filteredRepoCache.Count())
for _, repo := range s.filteredRepoCache.Values() {
r, ok := repo.(string)
if !ok {
ctx.Logger().Error(fmt.Errorf("type assertion failed"), "unexpected value in cache", "repo", repo)
continue
}
s.repos = append(s.repos, r)
}
githubReposEnumerated.WithLabelValues(s.name).Set(float64(len(s.repos)))
s.log.Info("Completed enumeration", "num_repos", len(s.repos), "num_orgs", s.orgsCache.Count(), "num_members", len(s.memberCache))
@@ -541,7 +548,6 @@ func (s *Source) enumerateWithToken(ctx context.Context, apiEndpoint, token stri
// If we're using public GitHub, make a regular client.
// Otherwise, make an enterprise client.
var isGHE bool = apiEndpoint != "https://api.github.com"
ghClient, err := createGitHubClient(s.httpClient, apiEndpoint)
if err != nil {
s.log.Error(err, "error creating GitHub client")
@@ -568,7 +574,7 @@ func (s *Source) enumerateWithToken(ctx context.Context, apiEndpoint, token stri
continue
}
if err != nil {
return errors.New(err)
return fmt.Errorf("error getting user: %w", err)
}
break
}
@@ -597,6 +603,7 @@ func (s *Source) enumerateWithToken(ctx context.Context, apiEndpoint, token stri
s.log.Error(err, "error fetching repos by user")
}
isGHE := !strings.EqualFold(apiEndpoint, cloudEndpoint)
if isGHE {
s.addAllVisibleOrgs(ctx)
} else {
@@ -644,12 +651,12 @@ func (s *Source) enumerateWithToken(ctx context.Context, apiEndpoint, token stri
func (s *Source) enumerateWithApp(ctx context.Context, apiEndpoint string, app *credentialspb.GitHubApp) (installationClient *github.Client, err error) {
installationID, err := strconv.ParseInt(app.InstallationId, 10, 64)
if err != nil {
return nil, errors.New(err)
return nil, err
}
appID, err := strconv.ParseInt(app.AppId, 10, 64)
if err != nil {
return nil, errors.New(err)
return nil, err
}
// This client is required to create installation tokens for cloning.
@@ -662,16 +669,16 @@ func (s *Source) enumerateWithApp(ctx context.Context, apiEndpoint string, app *
appID,
[]byte(app.PrivateKey))
if err != nil {
return nil, errors.New(err)
return nil, err
}
appItr.BaseURL = apiEndpoint
// Does this need to be separate from |s.httpClient|?
instHttpClient := common.RetryableHttpClientTimeout(60)
instHttpClient.Transport = appItr
installationClient, err = github.NewEnterpriseClient(apiEndpoint, apiEndpoint, instHttpClient)
instHTTPClient := common.RetryableHttpClientTimeout(60)
instHTTPClient.Transport = appItr
installationClient, err = github.NewEnterpriseClient(apiEndpoint, apiEndpoint, instHTTPClient)
if err != nil {
return nil, errors.New(err)
return nil, err
}
// This client is used for most APIs.
@@ -681,14 +688,14 @@ func (s *Source) enumerateWithApp(ctx context.Context, apiEndpoint string, app *
installationID,
[]byte(app.PrivateKey))
if err != nil {
return nil, errors.New(err)
return nil, err
}
itr.BaseURL = apiEndpoint
s.httpClient.Transport = itr
s.apiClient, err = github.NewEnterpriseClient(apiEndpoint, apiEndpoint, s.httpClient)
if err != nil {
return nil, errors.New(err)
return nil, err
}
// If no repos were provided, enumerate them.
@@ -719,19 +726,14 @@ func (s *Source) enumerateWithApp(ctx context.Context, apiEndpoint string, app *
return installationClient, nil
}
func createGitHubClient(httpClient *http.Client, apiEndpoint string) (ghClient *github.Client, err error) {
func createGitHubClient(httpClient *http.Client, apiEndpoint string) (*github.Client, error) {
// If we're using public GitHub, make a regular client.
// Otherwise, make an enterprise client.
if apiEndpoint == "https://api.github.com" {
ghClient = github.NewClient(httpClient)
} else {
ghClient, err = github.NewEnterpriseClient(apiEndpoint, apiEndpoint, httpClient)
if err != nil {
return nil, errors.New(err)
}
if strings.EqualFold(apiEndpoint, cloudEndpoint) {
return github.NewClient(httpClient), nil
}
return ghClient, err
return github.NewEnterpriseClient(apiEndpoint, apiEndpoint, httpClient)
}
func (s *Source) scan(ctx context.Context, installationClient *github.Client, chunksChan chan *sources.Chunk) error {
@@ -960,7 +962,7 @@ func (s *Source) addAllVisibleOrgs(ctx context.Context) {
continue
}
if err != nil {
s.log.Error(err, "Could not list all organizations")
s.log.Error(err, "could not list all organizations")
return
}
if len(orgs) == 0 {
@@ -972,11 +974,12 @@ func (s *Source) addAllVisibleOrgs(ctx context.Context) {
for _, org := range orgs {
var name string
if org.Name != nil {
switch {
case org.Name != nil:
name = *org.Name
} else if org.Login != nil {
case org.Login != nil:
name = *org.Login
} else {
default:
continue
}
s.orgsCache.Set(name, name)
@@ -1037,7 +1040,7 @@ func (s *Source) addMembersByOrg(ctx context.Context, org string) error {
continue
}
if err != nil || len(members) == 0 {
return errors.New("Could not list organization members: account may not have access to list organization members")
return fmt.Errorf("could not list organization members: account may not have access to list organization members %w", err)
}
if res == nil {
break
@@ -1516,7 +1519,7 @@ func (s *Source) scanTargets(ctx context.Context, targets []sources.ChunkingTarg
func (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget, chunksChan chan *sources.Chunk) error {
metaType, ok := target.QueryCriteria.GetData().(*source_metadatapb.MetaData_Github)
if !ok {
return fmt.Errorf("unable to cast metadata type for targetted scan")
return fmt.Errorf("unable to cast metadata type for targeted scan")
}
meta := metaType.Github
@@ -1547,6 +1550,7 @@ func (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget,
SourceName: s.name,
SourceID: s.SourceID(),
JobID: s.JobID(),
SecretID: target.SecretID,
Data: []byte(res),
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{Github: meta},
+1
View File
@@ -138,6 +138,7 @@ func (s *Source) Init(_ context.Context, name string, jobId sources.JobID, sourc
}
},
conn.GetSkipBinaries(),
conn.GetSkipArchives(),
)
return nil
+2 -2
View File
@@ -57,7 +57,7 @@ func TestSource_Scan(t *testing.T) {
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,
SourceName: "test source",
},
wantReposScanned: 2,
wantReposScanned: 4,
},
{
name: "token auth, enumerate repo, with glob ignore",
@@ -74,7 +74,7 @@ func TestSource_Scan(t *testing.T) {
SourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,
SourceName: "test source",
},
wantReposScanned: 2,
wantReposScanned: 4,
},
{
name: "token auth, scoped repo",
+19 -6
View File
@@ -229,17 +229,30 @@ func (s *SourceManager) run(ctx context.Context, source Source, report *JobProgr
}()
report.TrackProgress(source.GetProgress())
ctx = context.WithValues(ctx,
"job_id", report.JobID,
"source_id", report.SourceID,
"source_name", report.SourceName,
"source_type", source.Type().String(),
)
if ctx.Value("job_id") == "" {
ctx = context.WithValue(ctx, "job_id", report.JobID)
}
if ctx.Value("source_id") == "" {
ctx = context.WithValue(ctx, "source_id", report.SourceID)
}
if ctx.Value("source_name") == "" {
ctx = context.WithValue(ctx, "source_name", report.SourceName)
}
if ctx.Value("source_type") == "" {
ctx = context.WithValue(ctx, "source_type", source.Type().String())
}
// Check for the preferred method of tracking source units.
canUseSourceUnits := len(targets) == 0 && s.useSourceUnitsFunc != nil
if enumChunker, ok := source.(SourceUnitEnumChunker); ok && canUseSourceUnits && s.useSourceUnitsFunc() {
ctx.Logger().Info("running source",
"with_units", true)
return s.runWithUnits(ctx, enumChunker, report)
}
ctx.Logger().Info("running source",
"with_units", false,
"target_count", len(targets),
"source_manager_units_configurable", s.useSourceUnitsFunc != nil)
return s.runWithoutUnits(ctx, source, report, targets...)
}
+9 -2
View File
@@ -24,6 +24,9 @@ type Chunk struct {
SourceID SourceID
// JobID is the ID of the job that the Chunk originated from.
JobID JobID
// SecretID is the ID of the secret, if it exists.
// Only secrets that are being reverified will have a SecretID.
SecretID int64
// SourceType is the type of Source that produced the chunk.
SourceType sourcespb.SourceType
// SourceMetadata holds the context of where the Chunk was found.
@@ -43,6 +46,8 @@ type Chunk struct {
type ChunkingTarget struct {
// QueryCriteria represents specific parameters or conditions to target the chunking process.
QueryCriteria *source_metadatapb.MetaData
// SecretID is the ID of the secret.
SecretID int64
}
// Source defines the interface required to implement a source chunker.
@@ -225,8 +230,10 @@ type GitlabConfig struct {
type FilesystemConfig struct {
// Paths is the list of files and directories to scan.
Paths []string
// Filter is the filter to use to scan the source.
Filter *common.Filter
// IncludePathsFile is the path to a file containing a list of regexps to include in the scan.
IncludePathsFile string
// ExcludePathsFile is the path to a file containing a list of regexps to exclude from the scan.
ExcludePathsFile string
}
// S3Config defines the optional configuration for an S3 source.
+4
View File
@@ -303,6 +303,10 @@ func (s *Source) acceptUDPConnections(ctx context.Context, netListener net.Packe
if common.IsDone(ctx) {
return nil
}
err := netListener.SetDeadline(time.Now().Add(time.Second))
if err != nil {
ctx.Logger().V(2).Info("could not update connection deadline", "error", err)
}
input := make([]byte, 65535)
_, remote, err := netListener.ReadFrom(input)
if err != nil {
+12
View File
@@ -976,6 +976,18 @@ enum DetectorType {
Overloop = 965;
Ngrok = 966;
Replicate = 967;
Postgres = 968;
AzureActiveDirectoryApplicationSecret = 969;
AzureCacheForRedisAccessKey = 970;
AzureCosmosDBKeyIdentifiable = 971;
AzureDevopsPersonalAccessToken = 972;
AzureFunctionKey = 973;
AzureMLWebServiceClassicIdentifiableKey = 974;
AzureSasToken = 975;
AzureSearchAdminKey = 976;
AzureSearchQueryKey = 977;
AzureManagementCertificate = 978;
AzureSQL = 979;
}
message Result {
+21 -13
View File
@@ -96,6 +96,7 @@ message Bitbucket {
repeated string repositories = 5;
repeated string ignore_repos = 6;
bool skip_binaries = 7;
bool skip_archives = 8;
}
message CircleCI {
@@ -156,6 +157,8 @@ message Filesystem {
// paths when we no longer depend on the name in enterprise configs.
repeated string directories = 1;
repeated string paths = 2;
string include_paths_file = 3; // path to file containing newline separated list of paths
string exclude_paths_file = 4; // path to file containing newline separated list of paths
}
message GCS {
@@ -196,6 +199,7 @@ message Git {
// like head, base, bare, etc.
string uri = 13; // repository URL. https://, file://, or ssh://
bool skip_binaries = 14;
bool skip_archives = 15;
}
message GitLab {
@@ -208,6 +212,7 @@ message GitLab {
repeated string repositories = 5;
repeated string ignore_repos = 6;
bool skip_binaries = 7;
bool skip_archives = 8;
}
message GitHub {
@@ -224,12 +229,13 @@ message GitHub {
bool includeForks = 8;
string head = 9;
string base = 10;
repeated string ignoreRepos = 11;
repeated string includeRepos = 12;
bool includePullRequestComments = 14;
bool includeIssueComments = 15;
bool includeGistComments = 16;
repeated string ignore_repos = 11;
repeated string include_repos = 12;
bool include_pull_request_comments = 14;
bool include_issue_comments = 15;
bool include_gist_comments = 16;
bool skip_binaries = 17;
bool skip_archives = 18;
}
message GoogleDrive {
@@ -282,7 +288,7 @@ message Slack {
credentials.SlackTokens tokens = 5;
}
repeated string channels = 3;
repeated string ignoreList = 4;
repeated string ignore_list = 4;
}
message Test{}
@@ -301,6 +307,7 @@ message Gerrit {
}
repeated string projects = 4;
bool skip_binaries = 5;
bool skip_archives = 6;
}
message Jenkins {
@@ -320,13 +327,13 @@ message Teams {
credentials.Oauth2 oauth = 7;
}
repeated string channels = 4;
repeated string ignoreList = 5;
repeated string ignore_list = 5;
repeated string team_ids = 6;
}
message Syslog {
string protocol = 1;
string listenAddress = 2;
string listen_address = 2;
string tlsCert = 3;
string tlsKey = 4;
string format = 5;
@@ -363,10 +370,11 @@ message AzureRepos {
repeated string repositories = 4;
repeated string organizations = 5;
repeated string projects = 6;
bool includeForks = 7;
repeated string ignoreRepos = 8;
repeated string includeRepos = 9;
repeated string includeProjects = 10;
repeated string ignoreProjects = 11;
bool include_forks = 7;
repeated string ignore_repos = 8;
repeated string include_repos = 9;
repeated string include_projects = 10;
repeated string ignore_projects = 11;
bool skip_binaries = 12;
bool skip_archives = 13;
}
+35 -2
View File
@@ -9,6 +9,7 @@ $this: download go binaries for trufflesecurity/trufflehog
Usage: $this [-b] bindir [-d] [tag]
-b sets bindir or installation directory, Defaults to ./bin
-d turns on debug logging
-v verify checksum signature. Require cosign binary to be installed.
[tag] is a tag from
https://github.com/trufflesecurity/trufflehog/releases
If tag is missing, then the latest will be used.
@@ -22,10 +23,11 @@ parse_args() {
# over-ridden by flag below
BINDIR=${BINDIR:-./bin}
while getopts "b:dh?x" arg; do
while getopts "b:dvh?x" arg; do
case "$arg" in
b) BINDIR="$OPTARG" ;;
d) log_set_priority 10 ;;
v) VERIFY_SIGN=true;;
h | \?) usage "$0" ;;
x) set -x ;;
esac
@@ -41,8 +43,15 @@ parse_args() {
execute() {
tmpdir=$(mktemp -d)
log_debug "downloading files into ${tmpdir}"
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
if [ "$VERIFY_SIGN" = true ]; then
http_download "${tmpdir}/${CHECKSUM}.${CERT_FORMAT}" "${CHECKSUM_URL}.${CERT_FORMAT}"
http_download "${tmpdir}/${CHECKSUM}.${SIG_FORMAT}" "${CHECKSUM_URL}.${SIG_FORMAT}"
verify_sign "${tmpdir}/${CHECKSUM}" "${tmpdir}/${CHECKSUM}.${CERT_FORMAT}" "${tmpdir}/${CHECKSUM}.${SIG_FORMAT}"
fi
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
srcdir="${tmpdir}"
(cd "${tmpdir}" && untar "${TARBALL}")
@@ -326,6 +335,24 @@ hash_sha256_verify() {
fi
}
check_cosign_bin() {
if [ "$VERIFY_SIGN" = true ]; then
if [ ! -x "$(command -v "$COSIGN_BINARY")" ]; then
log_err "Cosign binary is not installed. Follow steps from https://docs.sigstore.dev/system_config/installation/ to install it."
return 1
fi
fi
}
verify_sign() {
log_debug "Verifying artifact $1"
${COSIGN_BINARY} verify-blob "$1" \
--certificate "$2" \
--signature "$3" \
--certificate-identity-regexp "https://github\.com/${OWNER}/${REPO}/\.github/workflows/.+" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
}
cat /dev/null <<EOF
------------------------------------------------------------------------
End of functions from https://github.com/client9/shlib
@@ -341,6 +368,10 @@ ARCH=$(uname_arch)
PREFIX="$OWNER/$REPO"
PLATFORM="${OS}/${ARCH}"
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
COSIGN_BINARY=cosign
VERIFY_SIGN=false
CERT_FORMAT=pem
SIG_FORMAT=sig
# use in logging routines
log_prefix() {
@@ -353,6 +384,8 @@ uname_arch_check "$ARCH"
parse_args "$@"
check_cosign_bin
get_binary
tag_to_version